Bug 62870 - Templates : Add ability to provide parameters
Contributed by UbikLoadPack (https://ubikloadpack.com)
This closes #432
Bugzilla Id: 62870
git-svn-id: https://svn.apache.org/repos/asf/jmeter/trunk@1847594 13f79535-47bb-0310-9956-ffa450edef68
Former-commit-id: 4909935a04
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<jmeterTestPlan version="1.2" properties="4.0" jmeter="4.0-SNAPSHOT.20180218">
|
||||
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.1-SNAPSHOT.20181118">
|
||||
<hashTree>
|
||||
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
|
||||
<stringProp name="TestPlan.comments"></stringProp>
|
||||
|
|
@ -12,16 +12,27 @@
|
|||
</TestPlan>
|
||||
<hashTree>
|
||||
<Arguments guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
<collectionProp name="Arguments.arguments">
|
||||
<elementProp name="host" elementType="Argument">
|
||||
<stringProp name="Argument.name">host</stringProp>
|
||||
<stringProp name="Argument.value">[=hostToRecord]</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
</elementProp>
|
||||
<elementProp name="scheme" elementType="Argument">
|
||||
<stringProp name="Argument.name">scheme</stringProp>
|
||||
<stringProp name="Argument.value">[=schemeToRecord]</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
</elementProp>
|
||||
</collectionProp>
|
||||
</Arguments>
|
||||
<hashTree/>
|
||||
<ConfigTestElement guiclass="HttpDefaultsGui" testclass="ConfigTestElement" testname="HTTP Request Defaults" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
||||
<stringProp name="HTTPSampler.domain">[=hostToRecord]</stringProp>
|
||||
<stringProp name="HTTPSampler.port"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol">[=schemeToRecord]</stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path"></stringProp>
|
||||
<stringProp name="HTTPSampler.concurrentPool">6</stringProp>
|
||||
|
|
@ -90,9 +101,8 @@
|
|||
<ProxyControl guiclass="ProxyControlGui" testclass="ProxyControl" testname="HTTP(S) Test Script Recorder" enabled="false">
|
||||
<stringProp name="ProxyControlGui.port">8888</stringProp>
|
||||
<collectionProp name="ProxyControlGui.exclude_list">
|
||||
<stringProp name="1301401588">.*toolbar\.live\.com.*</stringProp>
|
||||
<stringProp name="1179605444">(?i).*\.(bmp|css|js|gif|ico|jpe?g|png|swf|eot|otf|ttf|mp4|woff|woff2)</stringProp>
|
||||
<stringProp name="1276958334">update\.microsoft\.com.*</stringProp>
|
||||
<stringProp name="-88591710">www\.download\.windowsupdate\.com.*</stringProp>
|
||||
<stringProp name="195066122">toolbarqueries\.google\..*</stringProp>
|
||||
<stringProp name="-1570593883">clients.*\.google.*</stringProp>
|
||||
<stringProp name="339269285">api\.bing\.com.*</stringProp>
|
||||
|
|
@ -171,7 +181,7 @@
|
|||
<connectTime>true</connectTime>
|
||||
</value>
|
||||
</objProp>
|
||||
<stringProp name="filename">recording.xml</stringProp>
|
||||
<stringProp name="filename">[=recordingOutputFile]</stringProp>
|
||||
</ResultCollector>
|
||||
<hashTree/>
|
||||
</hashTree>
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@
|
|||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- Basic DTD for podlings.xml -->
|
||||
<!-- Basic DTD for templates.xml -->
|
||||
<!ELEMENT templates (template+)>
|
||||
|
||||
<!ELEMENT template (name, fileName, description)>
|
||||
<!ELEMENT template (name, fileName, description, parameters?)>
|
||||
<!-- Whether the template is a complete test plan or not -->
|
||||
<!ATTLIST template isTestPlan (true|false) #REQUIRED>
|
||||
|
||||
|
|
@ -27,3 +27,11 @@
|
|||
<!ELEMENT fileName (#PCDATA) >
|
||||
|
||||
<!ELEMENT description (#PCDATA) >
|
||||
|
||||
<!ELEMENT parameters (parameter*)>
|
||||
|
||||
<!ELEMENT parameter EMPTY>
|
||||
|
||||
<!ATTLIST parameter key CDATA #REQUIRED>
|
||||
|
||||
<!ATTLIST parameter defaultValue CDATA #REQUIRED>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
<!--
|
||||
See the DTD for allowable elements and attributes.
|
||||
-->
|
||||
<!DOCTYPE templates SYSTEM "templates.dtd">
|
||||
<!DOCTYPE templates SYSTEM "templates.dtd">
|
||||
<templates>
|
||||
<template isTestPlan="true">
|
||||
<name>Recording</name>
|
||||
|
|
@ -42,6 +42,11 @@
|
|||
<li><a href="http://jmeter.apache.org/usermanual/component_reference.html#HTTP_Proxy_Server" >http://jmeter.apache.org/usermanual/component_reference.html#HTTP_Proxy_Server</a></li>
|
||||
</ul>
|
||||
]]></description>
|
||||
<parameters>
|
||||
<parameter defaultValue="recording.xml" key="recordingOutputFile"/>
|
||||
<parameter defaultValue="www.example.com" key="hostToRecord"/>
|
||||
<parameter defaultValue="https" key="schemeToRecord"/>
|
||||
</parameters>
|
||||
</template>
|
||||
<template isTestPlan="true">
|
||||
<name>Recording with Think Time</name>
|
||||
|
|
|
|||
|
|
@ -1057,8 +1057,8 @@ run JMeter unless all the JMeter jars are added.
|
|||
<fileset dir="${src.core}" includes="**/*.properties">
|
||||
<exclude name="*eucJP*"/>
|
||||
</fileset>
|
||||
<fileset dir="${src.core}" includes="**/*.xml">
|
||||
</fileset>
|
||||
<fileset dir="${src.core}" includes="**/*.xml" />
|
||||
<fileset dir="${src.core}" includes="**/*.dtd" />
|
||||
<!-- This file is used by the jmeter -h option -->
|
||||
<fileset dir="${src.core}" includes="org/apache/jmeter/help.txt"/>
|
||||
</jar>
|
||||
|
|
@ -1703,6 +1703,7 @@ run JMeter unless all the JMeter jars are added.
|
|||
<zipfileset dir="${dest.jar.jmeter}" prefix="bin" includes="log4j2.xml"/>
|
||||
<zipfileset dir="${dest.jar.jmeter}" prefix="bin" includes="proxyserver.jks"/>
|
||||
<zipfileset dir="${dest.jar.jmeter}" prefix="bin" includes="users.dtd"/>
|
||||
<zipfileset dir="${dest.jar.jmeter}" prefix="bin" includes="templates/templates.dtd"/>
|
||||
<zipfileset dir="${dest.jar.jmeter}" prefix="bin" includes="users.xml"/>
|
||||
<zipfileset dir="${dest.jar.jmeter}" prefix="bin" includes="report-template/**/*.*" />
|
||||
</jar>
|
||||
|
|
|
|||
|
|
@ -22,10 +22,20 @@ import java.awt.BorderLayout;
|
|||
import java.awt.Dimension;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.Font;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.HeadlessException;
|
||||
import java.awt.Insets;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
|
|
@ -35,6 +45,7 @@ import javax.swing.JButton;
|
|||
import javax.swing.JComponent;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JRootPane;
|
||||
|
|
@ -50,16 +61,21 @@ import org.apache.jmeter.gui.action.template.Template;
|
|||
import org.apache.jmeter.gui.action.template.TemplateManager;
|
||||
import org.apache.jmeter.swing.HtmlPane;
|
||||
import org.apache.jmeter.util.JMeterUtils;
|
||||
import org.apache.jmeter.util.TemplateUtil;
|
||||
import org.apache.jorphan.gui.ComponentUtil;
|
||||
import org.apache.jorphan.gui.JLabeledChoice;
|
||||
import org.apache.jorphan.gui.JLabeledTextField;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.TemplateException;
|
||||
|
||||
/**
|
||||
* Dialog used for Templates selection
|
||||
* @since 2.10
|
||||
*/
|
||||
public class SelectTemplatesDialog extends JDialog implements ChangeListener, ActionListener, HyperlinkListener {
|
||||
public class SelectTemplatesDialog extends JDialog implements ChangeListener, ActionListener, HyperlinkListener { // NOSONAR Ignore inheritence warning
|
||||
|
||||
private static final long serialVersionUID = 1;
|
||||
|
||||
|
|
@ -83,7 +99,13 @@ public class SelectTemplatesDialog extends JDialog implements ChangeListener, Ac
|
|||
|
||||
private final JButton cancelButton = new JButton(JMeterUtils.getResString("cancel")); //$NON-NLS-1$
|
||||
|
||||
private final JScrollPane scroller = new JScrollPane(helpDoc);
|
||||
private final JButton previous = new JButton(JMeterUtils.getResString("previous")); //$NON-NLS-1$
|
||||
|
||||
private final JButton validateButton = new JButton(JMeterUtils.getResString("validate_threadgroup")); //$NON-NLS-1$
|
||||
|
||||
private Map<String, JLabeledTextField> parametersTextFields = new LinkedHashMap<>();
|
||||
|
||||
private JPanel actionBtnBar = new JPanel(new FlowLayout());
|
||||
|
||||
public SelectTemplatesDialog() {
|
||||
super((JFrame) null, JMeterUtils.getResString("template_title"), true); //$NON-NLS-1$
|
||||
|
|
@ -127,7 +149,8 @@ public class SelectTemplatesDialog extends JDialog implements ChangeListener, Ac
|
|||
|
||||
/**
|
||||
* Check if existing Test Plan has been modified and ask user
|
||||
* what he wants to do if test plan is dirty
|
||||
* what he wants to do if test plan is dirty.
|
||||
* Also ask user for parameters in case of customizable templates.
|
||||
* @param actionEvent {@link ActionEvent}
|
||||
*/
|
||||
private void checkDirtyAndLoad(final ActionEvent actionEvent)
|
||||
|
|
@ -137,67 +160,138 @@ public class SelectTemplatesDialog extends JDialog implements ChangeListener, Ac
|
|||
if (template == null) {
|
||||
return;
|
||||
}
|
||||
templateList.setValues(TemplateManager.getInstance().reset().getTemplateNames()); // reload the templates before loading
|
||||
|
||||
final boolean isTestPlan = template.isTestPlan();
|
||||
// Check if the user wants to drop any changes
|
||||
if (isTestPlan) {
|
||||
ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.CHECK_DIRTY));
|
||||
GuiPackage guiPackage = GuiPackage.getInstance();
|
||||
if (guiPackage.isDirty()) {
|
||||
// Check if the user wants to create from template
|
||||
int response = JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(),
|
||||
JMeterUtils.getResString("cancel_new_from_template"), // $NON-NLS-1$
|
||||
JMeterUtils.getResString("template_load?"), // $NON-NLS-1$
|
||||
JOptionPane.YES_NO_CANCEL_OPTION,
|
||||
JOptionPane.QUESTION_MESSAGE);
|
||||
if(response == JOptionPane.YES_OPTION) {
|
||||
ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.SAVE));
|
||||
}
|
||||
if (response == JOptionPane.CLOSED_OPTION || response == JOptionPane.CANCEL_OPTION) {
|
||||
return; // Don't clear the plan
|
||||
}
|
||||
}
|
||||
if (isTestPlan && !checkDirty(actionEvent)) {
|
||||
return;
|
||||
}
|
||||
ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.STOP_THREAD));
|
||||
final File parent = template.getParent();
|
||||
final File fileToCopy = parent != null
|
||||
File fileToCopy = parent != null
|
||||
? new File(parent, template.getFileName())
|
||||
: new File(JMeterUtils.getJMeterHome(), template.getFileName());
|
||||
Load.loadProjectFile(actionEvent, fileToCopy, !isTestPlan, false);
|
||||
this.setVisible(false);
|
||||
: new File(JMeterUtils.getJMeterHome(), template.getFileName());
|
||||
replaceTemplateParametersAndLoad(actionEvent, template, isTestPlan, fileToCopy);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param actionEvent {@link ActionEvent}
|
||||
* @param template {@link Template} definition
|
||||
* @param isTestPlan If it's a full test plan or a part
|
||||
* @param templateFile Template file to load
|
||||
*/
|
||||
void replaceTemplateParametersAndLoad(final ActionEvent actionEvent, final Template template,
|
||||
final boolean isTestPlan, File templateFile) {
|
||||
File temporaryGeneratedFile = null;
|
||||
try {
|
||||
// handle customized templates (the .jmx.fmkr files)
|
||||
if (template.getParameters() != null && !template.getParameters().isEmpty()) {
|
||||
File jmxFile = new File(templateFile.getAbsolutePath());
|
||||
Map<String, String> userParameters = getUserParameters();
|
||||
Configuration templateCfg = TemplateUtil.getTemplateConfig();
|
||||
try {
|
||||
temporaryGeneratedFile = File.createTempFile(template.getName(), ".output");
|
||||
templateFile = temporaryGeneratedFile;
|
||||
TemplateUtil.processTemplate(jmxFile, temporaryGeneratedFile, templateCfg, userParameters);
|
||||
} catch (IOException | TemplateException ex) {
|
||||
log.error("Error generating output file {} from template {}", temporaryGeneratedFile, jmxFile, ex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Load.loadProjectFile(actionEvent, templateFile, !isTestPlan, false);
|
||||
this.dispose();
|
||||
} finally {
|
||||
if (temporaryGeneratedFile != null && !temporaryGeneratedFile.delete()) {
|
||||
log.warn("Could not delete generated output file {} from template {}", temporaryGeneratedFile, templateFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param actionEvent {@link ActionEvent}
|
||||
* @return true if plan is not dirty or has been saved
|
||||
*/
|
||||
boolean checkDirty(final ActionEvent actionEvent) {
|
||||
ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.CHECK_DIRTY));
|
||||
GuiPackage guiPackage = GuiPackage.getInstance();
|
||||
if (guiPackage.isDirty()) {
|
||||
// Check if the user wants to create from template
|
||||
int response = JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(),
|
||||
JMeterUtils.getResString("cancel_new_from_template"), // $NON-NLS-1$
|
||||
JMeterUtils.getResString("template_load?"), // $NON-NLS-1$
|
||||
JOptionPane.YES_NO_CANCEL_OPTION,
|
||||
JOptionPane.QUESTION_MESSAGE);
|
||||
if (response == JOptionPane.YES_OPTION) {
|
||||
ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.SAVE));
|
||||
return true;
|
||||
}
|
||||
if (response == JOptionPane.CLOSED_OPTION || response == JOptionPane.CANCEL_OPTION) {
|
||||
return false; // Don't clear the plan
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private Map<String, String> getUserParameters(){
|
||||
Map<String, String> userParameters = new LinkedHashMap<>();
|
||||
for (Entry<String, JLabeledTextField> entry : parametersTextFields.entrySet()) {
|
||||
userParameters.put(entry.getKey(), entry.getValue().getText());
|
||||
}
|
||||
return userParameters;
|
||||
}
|
||||
|
||||
private void init() { // WARNING: called from ctor so must not be overridden (i.e. must be private or final)
|
||||
templateList.setValues(TemplateManager.getInstance().getTemplateNames());
|
||||
templateList.setValues(TemplateManager.getInstance().getTemplateNames());
|
||||
templateList.addChangeListener(this);
|
||||
reloadTemplateButton.addActionListener(this);
|
||||
reloadTemplateButton.setFont(FONT_SMALL);
|
||||
this.getContentPane().setLayout(new BorderLayout(10, 0));
|
||||
|
||||
JPanel templateBar = new JPanel(new BorderLayout());
|
||||
templateBar.add(templateList, BorderLayout.CENTER);
|
||||
JPanel reloadBtnBar = new JPanel();
|
||||
reloadBtnBar.add(reloadTemplateButton);
|
||||
templateBar.add(reloadBtnBar, BorderLayout.EAST);
|
||||
this.getContentPane().add(templateBar, BorderLayout.NORTH);
|
||||
helpDoc.setContentType("text/html"); //$NON-NLS-1$
|
||||
helpDoc.setEditable(false);
|
||||
helpDoc.addHyperlinkListener(this);
|
||||
this.getContentPane().add(scroller, BorderLayout.CENTER);
|
||||
|
||||
applyTemplateButton.addActionListener(this);
|
||||
cancelButton.addActionListener(this);
|
||||
|
||||
// Bottom buttons bar
|
||||
JPanel actionBtnBar = new JPanel(new FlowLayout());
|
||||
actionBtnBar.add(applyTemplateButton);
|
||||
actionBtnBar.add(cancelButton);
|
||||
this.getContentPane().add(actionBtnBar, BorderLayout.SOUTH);
|
||||
previous.addActionListener(this);
|
||||
validateButton.addActionListener(this);
|
||||
|
||||
// allow to reset the JDialog if the user click on the close button while
|
||||
// it was displaying templates parameters
|
||||
this.addWindowListener(new WindowAdapter(){
|
||||
@Override
|
||||
public void windowClosing(WindowEvent evt){
|
||||
resetJDialog();
|
||||
dispose();
|
||||
}
|
||||
});
|
||||
this.setContentPane(templateSelectionPanel());
|
||||
|
||||
this.pack();
|
||||
this.setMinimumSize(new Dimension(MINIMAL_BOX_WIDTH, MINIMAL_BOX_HEIGHT));
|
||||
ComponentUtil.centerComponentInWindow(this, 50); // center position and 50% of screen size
|
||||
populateTemplatePage();
|
||||
}
|
||||
|
||||
private JPanel templateSelectionPanel() {
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
|
||||
JScrollPane scroller = new JScrollPane();
|
||||
scroller.setViewportView(helpDoc);
|
||||
JPanel templateBar = new JPanel(new BorderLayout());
|
||||
templateBar.add(templateList, BorderLayout.CENTER);
|
||||
JPanel reloadBtnBar = new JPanel();
|
||||
reloadBtnBar.add(reloadTemplateButton);
|
||||
templateBar.add(reloadBtnBar, BorderLayout.EAST);
|
||||
|
||||
// Bottom buttons bar
|
||||
actionBtnBar.add(applyTemplateButton);
|
||||
actionBtnBar.add(cancelButton);
|
||||
|
||||
panel.add(templateBar, BorderLayout.NORTH);
|
||||
panel.add(scroller, BorderLayout.CENTER);
|
||||
panel.add(actionBtnBar, BorderLayout.SOUTH);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do search
|
||||
|
|
@ -207,19 +301,44 @@ public class SelectTemplatesDialog extends JDialog implements ChangeListener, Ac
|
|||
public void actionPerformed(ActionEvent e) {
|
||||
final Object source = e.getSource();
|
||||
if (source == cancelButton) {
|
||||
this.setVisible(false);
|
||||
return;
|
||||
resetJDialog();
|
||||
this.dispose();
|
||||
} else if (source == applyTemplateButton) {
|
||||
checkDirtyAndLoad(e);
|
||||
} else if (source == reloadTemplateButton) {
|
||||
templateList.setValues(TemplateManager.getInstance().reset().getTemplateNames());
|
||||
String selectedTemplate = templateList.getText();
|
||||
Template template = TemplateManager.getInstance().getTemplateByName(selectedTemplate);
|
||||
if (hasParameters(template)) {
|
||||
this.setContentPane(configureParametersPanel(template.getParameters()));
|
||||
this.revalidate();
|
||||
} else {
|
||||
checkDirtyAndLoad(e);
|
||||
}
|
||||
} else if (source == reloadTemplateButton || source == previous) {
|
||||
resetJDialog();
|
||||
} else if (source == validateButton) {
|
||||
checkDirtyAndLoad(e);
|
||||
resetJDialog();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param template {@link Template}
|
||||
* @return true if template has not parameter
|
||||
*/
|
||||
private boolean hasParameters(Template template) {
|
||||
return !(template.getParameters() == null || template.getParameters().isEmpty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent event) {
|
||||
populateTemplatePage();
|
||||
}
|
||||
|
||||
private void resetJDialog() {
|
||||
templateList.setValues(TemplateManager.getInstance().reset().getTemplateNames()); // reload templates
|
||||
this.setContentPane(templateSelectionPanel());
|
||||
this.revalidate();
|
||||
}
|
||||
|
||||
private void populateTemplatePage() {
|
||||
String selectedTemplate = templateList.getText();
|
||||
|
|
@ -230,6 +349,65 @@ public class SelectTemplatesDialog extends JDialog implements ChangeListener, Ac
|
|||
: JMeterUtils.getResString("template_merge_from") );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param parameters {@link Map} parameters map
|
||||
* @return JPanel from parameters
|
||||
*/
|
||||
private JPanel configureParametersPanel(Map<String, String> parameters) {
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
|
||||
JPanel northPanel = new JPanel(new FlowLayout());
|
||||
JLabel label = new JLabel(JMeterUtils.getResString("template_fill_parameters"));
|
||||
label.setPreferredSize(new Dimension(150,35));
|
||||
northPanel.add(label);
|
||||
panel.add(northPanel, BorderLayout.NORTH);
|
||||
|
||||
parametersTextFields.clear();
|
||||
|
||||
GridBagConstraints gbc = new GridBagConstraints();
|
||||
initConstraints(gbc);
|
||||
int parameterCount = 0;
|
||||
|
||||
JPanel gridbagpanel = new JPanel(new GridBagLayout());
|
||||
for (Entry<String, String> entry : parameters.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
JLabeledTextField paramLabel = new JLabeledTextField(key + " : ");
|
||||
paramLabel.setText(value);
|
||||
parametersTextFields.put(key, paramLabel);
|
||||
|
||||
gbc.gridy = parameterCount++;
|
||||
List<JComponent> listedParamLabel = paramLabel.getComponentList();
|
||||
gridbagpanel.add(listedParamLabel.get(0), gbc.clone());
|
||||
gbc.gridx = 1;
|
||||
gridbagpanel.add(listedParamLabel.get(1), gbc.clone());
|
||||
gbc.gridx = 0;
|
||||
}
|
||||
|
||||
JPanel actionBtnBarParameterPanel = new JPanel(new FlowLayout());
|
||||
actionBtnBarParameterPanel.add(validateButton);
|
||||
actionBtnBarParameterPanel.add(cancelButton);
|
||||
actionBtnBarParameterPanel.add(previous);
|
||||
|
||||
JScrollPane scroller = new JScrollPane(gridbagpanel);
|
||||
panel.add(scroller, BorderLayout.CENTER);
|
||||
panel.add(actionBtnBarParameterPanel, BorderLayout.SOUTH);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
private void initConstraints(GridBagConstraints gbc) {
|
||||
gbc.anchor = GridBagConstraints.WEST;
|
||||
gbc.insets = new Insets(0,0,5,0);
|
||||
gbc.fill = GridBagConstraints.NONE;
|
||||
gbc.gridheight = 1;
|
||||
gbc.gridwidth = 1;
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 0;
|
||||
gbc.weightx = 0;
|
||||
gbc.weighty = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hyperlinkUpdate(HyperlinkEvent e) {
|
||||
if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED && java.awt.Desktop.isDesktopSupported()) {
|
||||
|
|
@ -240,5 +418,4 @@ public class SelectTemplatesDialog extends JDialog implements ChangeListener, Ac
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,10 +19,12 @@
|
|||
package org.apache.jmeter.gui.action.template;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Template Bean
|
||||
* @since 2.10
|
||||
* @since 2.10
|
||||
*/
|
||||
public class Template {
|
||||
private boolean isTestPlan;
|
||||
|
|
@ -30,6 +32,7 @@ public class Template {
|
|||
private String fileName;
|
||||
private String description;
|
||||
private transient File parent; // for relative links
|
||||
private Map<String, String> parameters;
|
||||
/**
|
||||
* @return the name
|
||||
*/
|
||||
|
|
@ -78,4 +81,116 @@ public class Template {
|
|||
public void setParent(File parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public Map<String, String> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public void setParameters(Map<String, String> parameters) {
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((description == null) ? 0 : description.hashCode());
|
||||
result = prime * result + ((fileName == null) ? 0 : fileName.hashCode());
|
||||
result = prime * result + (isTestPlan ? 1231 : 1237);
|
||||
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
||||
result = prime * result + ((parameters == null) ? 0 : parameters.hashCode());
|
||||
result = prime * result + ((parent == null) ? 0 : parent.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Template other = (Template) obj;
|
||||
if (description == null) {
|
||||
if (other.description != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!description.equals(other.description)) {
|
||||
return false;
|
||||
}
|
||||
if (fileName == null) {
|
||||
if (other.fileName != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!fileName.equals(other.fileName)) {
|
||||
return false;
|
||||
}
|
||||
if (isTestPlan != other.isTestPlan) {
|
||||
return false;
|
||||
}
|
||||
if (name == null) {
|
||||
if (other.name != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!name.equals(other.name)) {
|
||||
return false;
|
||||
}
|
||||
if (!mapsEquals(parameters, other.parameters)) {
|
||||
return false;
|
||||
}
|
||||
if (parent == null) {
|
||||
if (other.parent != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!parent.equals(other.parent)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean mapsEquals(Map<String, String> map1, Map<String, String> map2) {
|
||||
if(map1 == null) {
|
||||
return map2 == null;
|
||||
}else if(map2 == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(map1.size() != map2.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(Entry<String, String> entry : map1.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
if(map2.containsKey(key)) {
|
||||
if(!map2.get(key).equals(value)) {
|
||||
return false;
|
||||
}
|
||||
}else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("Template [isTestPlan=");
|
||||
builder.append(isTestPlan);
|
||||
builder.append(", name=");
|
||||
builder.append(name);
|
||||
builder.append(", fileName=");
|
||||
builder.append(fileName);
|
||||
builder.append(", description=");
|
||||
builder.append(description);
|
||||
builder.append(", parameters=");
|
||||
builder.append(parameters);
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,12 @@
|
|||
package org.apache.jmeter.gui.action.template;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
|
|
@ -29,24 +32,21 @@ import org.apache.commons.lang3.StringUtils;
|
|||
import org.apache.jmeter.util.JMeterUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.thoughtworks.xstream.XStream;
|
||||
import com.thoughtworks.xstream.io.StreamException;
|
||||
import com.thoughtworks.xstream.io.xml.DomDriver;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.EntityResolver;
|
||||
import org.xml.sax.ErrorHandler;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.SAXParseException;
|
||||
|
||||
/**
|
||||
* Manages Test Plan templates
|
||||
* @since 2.10
|
||||
*/
|
||||
public class TemplateManager {
|
||||
// Created by XStream reading templates.xml
|
||||
private static class Templates {
|
||||
/*
|
||||
* N.B. Must use LinkedHashMap for field type
|
||||
* XStream creates a plain HashMap if one uses Map as the field type.
|
||||
*/
|
||||
private final LinkedHashMap<String, Template> templates = new LinkedHashMap<>();
|
||||
}
|
||||
private static final String TEMPLATE_FILES = JMeterUtils.getPropDefault("template.files", // $NON-NLS-1$
|
||||
"/bin/templates/templates.xml");
|
||||
|
||||
|
|
@ -56,52 +56,12 @@ public class TemplateManager {
|
|||
|
||||
private final Map<String, Template> allTemplates;
|
||||
|
||||
private final XStream xstream = initXStream();
|
||||
|
||||
public static TemplateManager getInstance() {
|
||||
return SINGLETON;
|
||||
}
|
||||
|
||||
private TemplateManager() {
|
||||
allTemplates = readTemplates();
|
||||
}
|
||||
|
||||
private XStream initXStream() {
|
||||
XStream xstream = new XStream(new DomDriver(){
|
||||
/**
|
||||
* Create the DocumentBuilderFactory instance.
|
||||
* See https://blog.compass-security.com/2012/08/secure-xml-parser-configuration/
|
||||
* See https://github.com/x-stream/xstream/issues/25
|
||||
* @return the new instance
|
||||
*/
|
||||
@Override
|
||||
protected DocumentBuilderFactory createDocumentBuilderFactory() {
|
||||
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
try {
|
||||
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
} catch (ParserConfigurationException e) {
|
||||
throw new StreamException(e);
|
||||
}
|
||||
factory.setExpandEntityReferences(false);
|
||||
return factory;
|
||||
}
|
||||
});
|
||||
JMeterUtils.setupXStreamSecurityPolicy(xstream);
|
||||
xstream.alias("template", Template.class);
|
||||
xstream.alias("templates", Templates.class);
|
||||
xstream.useAttributeFor(Template.class, "isTestPlan");
|
||||
|
||||
// templates i
|
||||
xstream.addImplicitMap(Templates.class,
|
||||
// field TemplateManager#templates
|
||||
"templates", // $NON-NLS-1$
|
||||
Template.class,
|
||||
// field Template#name
|
||||
"name" // $NON-NLS-1$
|
||||
);
|
||||
|
||||
return xstream;
|
||||
allTemplates = readTemplates();
|
||||
}
|
||||
|
||||
public void addTemplate(Template template) {
|
||||
|
|
@ -120,26 +80,26 @@ public class TemplateManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return the templates names
|
||||
* @return the templates names sorted in alphabetical order
|
||||
*/
|
||||
public String[] getTemplateNames() {
|
||||
return allTemplates.keySet().toArray(new String[allTemplates.size()]);
|
||||
}
|
||||
|
||||
private Map<String, Template> readTemplates() {
|
||||
final Map<String, Template> temps = new LinkedHashMap<>();
|
||||
final Map<String, Template> temps = new TreeMap<>();
|
||||
|
||||
final String[] templateFiles = TEMPLATE_FILES.split(",");
|
||||
for (String templateFile : templateFiles) {
|
||||
if(!StringUtils.isEmpty(templateFile)) {
|
||||
final File f = new File(JMeterUtils.getJMeterHome(), templateFile);
|
||||
final File file = new File(JMeterUtils.getJMeterHome(), templateFile);
|
||||
try {
|
||||
if(f.exists() && f.canRead()) {
|
||||
if(file.exists() && file.canRead()) {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Reading templates from: {}", f.getAbsolutePath());
|
||||
log.info("Reading templates from: {}", file.getAbsolutePath());
|
||||
}
|
||||
final File parent = f.getParentFile();
|
||||
final LinkedHashMap<String, Template> templates = ((Templates) xstream.fromXML(f)).templates;
|
||||
Map<String, Template> templates = parseTemplateFile(file);
|
||||
final File parent = file.getParentFile();
|
||||
for(Template t : templates.values()) {
|
||||
if (!t.getFileName().startsWith("/")) {
|
||||
t.setParent(parent);
|
||||
|
|
@ -149,12 +109,12 @@ public class TemplateManager {
|
|||
} else {
|
||||
if (log.isWarnEnabled()) {
|
||||
log.warn("Ignoring template file:'{}' as it does not exist or is not readable",
|
||||
f.getAbsolutePath());
|
||||
file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
} catch(Exception ex) {
|
||||
if (log.isWarnEnabled()) {
|
||||
log.warn("Ignoring template file:'{}', an error occurred parsing the file", f.getAbsolutePath(),
|
||||
log.warn("Ignoring template file:'{}', an error occurred parsing the file", file.getAbsolutePath(),
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
|
@ -162,6 +122,100 @@ public class TemplateManager {
|
|||
}
|
||||
return temps;
|
||||
}
|
||||
|
||||
public final class LoggingErrorHandler implements ErrorHandler {
|
||||
private Logger logger;
|
||||
private File file;
|
||||
|
||||
public LoggingErrorHandler(Logger logger, File file) {
|
||||
this.logger = logger;
|
||||
this.file = file;
|
||||
}
|
||||
@Override
|
||||
public void error(SAXParseException ex) throws SAXException {
|
||||
throw ex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fatalError(SAXParseException ex) throws SAXException {
|
||||
throw ex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(SAXParseException ex) throws SAXException {
|
||||
logger.warn("Warning parsing file {}", file, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DefaultEntityResolver implements EntityResolver {
|
||||
public DefaultEntityResolver() {
|
||||
super();
|
||||
}
|
||||
|
||||
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
|
||||
if(systemId.endsWith("templates.dtd")) {
|
||||
return new InputSource(TemplateManager.class.getResourceAsStream("/org/apache/jmeter/gui/action/template/templates.dtd"));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Template> parseTemplateFile(File file) throws IOException, SAXException, ParserConfigurationException{
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
dbf.setValidating(true);
|
||||
dbf.setNamespaceAware(true);
|
||||
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
DocumentBuilder bd = dbf.newDocumentBuilder();
|
||||
bd.setEntityResolver(new DefaultEntityResolver());
|
||||
LoggingErrorHandler errorHandler = new LoggingErrorHandler(log, file);
|
||||
bd.setErrorHandler(errorHandler);
|
||||
Document document = bd.parse(file.getAbsolutePath());
|
||||
document.getDocumentElement().normalize();
|
||||
Map<String, Template> templates = new TreeMap<>();
|
||||
NodeList templateNodes = document.getElementsByTagName("template");
|
||||
for (int i = 0; i < templateNodes.getLength(); i++) {
|
||||
Node node = templateNodes.item(i);
|
||||
parseTemplateNode(templates, node);
|
||||
}
|
||||
return templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param templates Map of {@link Template} referenced by name
|
||||
* @param templateNode {@link Node} the xml template node
|
||||
*/
|
||||
void parseTemplateNode(Map<String, Template> templates, Node templateNode) {
|
||||
if (templateNode.getNodeType() == Node.ELEMENT_NODE) {
|
||||
Template template = new Template();
|
||||
Element element = (Element) templateNode;
|
||||
template.setTestPlan("true".equals(element.getAttribute("isTestPlan")));
|
||||
template.setName(textOfFirstTag(element, "name"));
|
||||
template.setDescription(textOfFirstTag(element, "description"));
|
||||
template.setFileName(textOfFirstTag(element, "fileName"));
|
||||
NodeList nl = element.getElementsByTagName("parameters");
|
||||
if(nl.getLength()>0) {
|
||||
NodeList parameterNodes = ((Element) nl.item(0)).getElementsByTagName("parameter");
|
||||
Map<String, String> parameters = parseParameterNodes(parameterNodes);
|
||||
template.setParameters(parameters);
|
||||
}
|
||||
templates.put(template.getName(), template);
|
||||
}
|
||||
}
|
||||
|
||||
private String textOfFirstTag(Element element, String tagName) {
|
||||
return element.getElementsByTagName(tagName).item(0).getTextContent();
|
||||
}
|
||||
|
||||
private Map<String, String> parseParameterNodes(NodeList parameterNodes) {
|
||||
Map<String, String> parametersMap = new HashMap<>();
|
||||
for (int i = 0; i < parameterNodes.getLength(); i++) {
|
||||
Element element = (Element) parameterNodes.item(i);
|
||||
parametersMap.put(element.getAttribute("key"), element.getAttribute("defaultValue"));
|
||||
}
|
||||
return parametersMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param selectedTemplate Template name
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- Basic DTD for templates.xml -->
|
||||
<!ELEMENT templates (template+)>
|
||||
|
||||
<!ELEMENT template (name, fileName, description, parameters?)>
|
||||
<!-- Whether the template is a complete test plan or not -->
|
||||
<!ATTLIST template isTestPlan (true|false) #REQUIRED>
|
||||
|
||||
<!ELEMENT name (#PCDATA) >
|
||||
|
||||
<!ELEMENT fileName (#PCDATA) >
|
||||
|
||||
<!ELEMENT description (#PCDATA) >
|
||||
|
||||
<!ELEMENT parameters (parameter*)>
|
||||
|
||||
<!ELEMENT parameter EMPTY>
|
||||
|
||||
<!ATTLIST parameter key CDATA #REQUIRED>
|
||||
|
||||
<!ATTLIST parameter defaultValue CDATA #REQUIRED>
|
||||
|
|
@ -1206,6 +1206,7 @@ teardown_on_shutdown=Run tearDown Thread Groups after shutdown of main threads
|
|||
template_choose=Select Template
|
||||
template_create_from=Create
|
||||
template_field=Template ($i$ where i is capturing group number, starts at 1):
|
||||
template_fill_parameters=Fill your parameters\:
|
||||
template_load?=Load template?
|
||||
template_menu=Templates...
|
||||
template_merge_from=Merge
|
||||
|
|
|
|||
|
|
@ -1195,6 +1195,7 @@ teardown_on_shutdown=Ex\u00E9cuter le Groupe d'unit\u00E9s de fin m\u00EAme apr\
|
|||
template_choose=Choisir le mod\u00E8le
|
||||
template_create_from=Cr\u00E9er
|
||||
template_field=Canevas \:
|
||||
template_fill_parameters=Remplir les param\u00E8tres \:
|
||||
template_load?=Charger le mod\u00E8le ?
|
||||
template_menu=Mod\u00E8les...
|
||||
template_merge_from=Fusionner
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.apache.jmeter.util;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.TemplateException;
|
||||
import freemarker.template.TemplateExceptionHandler;
|
||||
|
||||
/**
|
||||
* Class used to process freemarkers templates
|
||||
* @since 5.1
|
||||
*/
|
||||
public final class TemplateUtil {
|
||||
|
||||
private static Configuration templateConfiguration = init();
|
||||
|
||||
private TemplateUtil() {
|
||||
super();
|
||||
}
|
||||
|
||||
private static Configuration init() {
|
||||
Configuration templateConfiguration = new Configuration(Configuration.getVersion());
|
||||
templateConfiguration.setDefaultEncoding(StandardCharsets.UTF_8.name());
|
||||
templateConfiguration.setInterpolationSyntax(Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX);
|
||||
templateConfiguration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
|
||||
return templateConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Give a basic templateConfiguration
|
||||
* @return a Configuration
|
||||
*/
|
||||
public static Configuration getTemplateConfig() {
|
||||
return templateConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a given freemarker template and put its result in a new folder.
|
||||
*
|
||||
* @param template file that contains the freemarker template to process
|
||||
* @param outputFile {@link File} created from template
|
||||
* @param templateConfig Configuration of the template
|
||||
* @param data to inject in the template
|
||||
* @throws IOException if an I/O exception occurs during writing to the writer
|
||||
* @throws TemplateException if an exception occurs during template processing
|
||||
*/
|
||||
public static void processTemplate(File template,
|
||||
File outputFile,
|
||||
Configuration templateConfig, Map<String, String> data)
|
||||
throws IOException, TemplateException {
|
||||
|
||||
templateConfig.setDirectoryForTemplateLoading(template.getParentFile());
|
||||
freemarker.template.Template temp = templateConfig.getTemplate(template.getName());
|
||||
try (FileOutputStream stream = new FileOutputStream(outputFile);
|
||||
Writer writer = new OutputStreamWriter(stream, StandardCharsets.UTF_8);
|
||||
BufferedWriter bufferedWriter = new BufferedWriter(writer)){
|
||||
temp.process(data, bufferedWriter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE templates SYSTEM "templates.dtd">
|
||||
<templates>
|
||||
<template isTestPlan="true">
|
||||
<name>Buggy</name>
|
||||
<fileName>/bin/templates/testTemplate.jmx.fmkr</fileName>
|
||||
<description><![CDATA[
|
||||
<h1>Test</h1>
|
||||
<h2>Overview</h2>
|
||||
This is a sample BeanShell sampler which shows how to use some of its
|
||||
features.
|
||||
<br />
|
||||
Please select a suitable location in the tree before merging.
|
||||
<h2>Useful links</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="http://jmeter.apache.org/usermanual/component_reference.html#BeanShell_Sampler">
|
||||
http://jmeter.apache.org/usermanual/component_reference.html#BeanShell_Sampler
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
]]></description>
|
||||
<parameters>
|
||||
<key>citrixPortalHost</key>
|
||||
<defaultValue>https://foo.com</defaultValue>
|
||||
</parameters>
|
||||
</template>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE templates SYSTEM "templates.dtd">
|
||||
<templates>
|
||||
<template isTestPlan="false">
|
||||
<name>testTemplateNotTestPlan</name>
|
||||
<fileName>/bin/templates/testTemplateNotTestPlan.jmx</fileName>
|
||||
<description>testTemplateNotTestPlan desc</description>
|
||||
</template>
|
||||
<template isTestPlan="true">
|
||||
<name>testTemplate</name>
|
||||
<fileName>/bin/templates/testTemplate.jmx</fileName>
|
||||
<description>testTemplate desc</description>
|
||||
</template>
|
||||
<template isTestPlan="true">
|
||||
<name>testTemplateWithParameters</name>
|
||||
<fileName>/bin/templates/testTemplate.jmx.fmkr</fileName>
|
||||
<description>Template with parameters</description>
|
||||
<parameters>
|
||||
<parameter key="testKey1" defaultValue="n 1" />
|
||||
<parameter key="testKey2" defaultValue="n 2" />
|
||||
<parameter key="testKey3" defaultValue="n 3" />
|
||||
</parameters>
|
||||
</template>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.apache.jmeter.gui.action.template;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.apache.jmeter.junit.JMeterTestCase;
|
||||
import org.junit.Test;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.SAXParseException;
|
||||
|
||||
/**
|
||||
* Test TemplateManager Class
|
||||
*/
|
||||
public class TestTemplateManager extends JMeterTestCase {
|
||||
|
||||
/**
|
||||
* Test a valid templateFile.
|
||||
*/
|
||||
@Test
|
||||
public void testTemplateFile() throws IOException, SAXException, ParserConfigurationException {
|
||||
File xmlTemplate = new File(this.getClass().getResource("validTemplates.xml").getFile());
|
||||
TemplateManager templateManager = TemplateManager.getInstance();
|
||||
Map<String, Template> templateMap = templateManager.parseTemplateFile(xmlTemplate);
|
||||
assertEquals(3, templateMap.size());
|
||||
Template testTemplate = templateMap.get("testTemplateWithParameters");
|
||||
assertTrue(testTemplate.isTestPlan());
|
||||
assertEquals("testTemplateWithParameters", testTemplate.getName());
|
||||
assertEquals("/bin/templates/testTemplate.jmx.fmkr", testTemplate.getFileName());
|
||||
assertEquals("Template with parameters", testTemplate.getDescription());
|
||||
Map<String, String> testTemplateParameters = testTemplate.getParameters();
|
||||
assertEquals("n 1", testTemplateParameters.get("testKey1"));
|
||||
assertEquals("n 2", testTemplateParameters.get("testKey2"));
|
||||
assertEquals("n 3", testTemplateParameters.get("testKey3"));
|
||||
|
||||
testTemplate = templateMap.get("testTemplateNotTestPlan");
|
||||
assertFalse(testTemplate.isTestPlan());
|
||||
assertEquals("testTemplateNotTestPlan", testTemplate.getName());
|
||||
assertEquals("/bin/templates/testTemplateNotTestPlan.jmx", testTemplate.getFileName());
|
||||
assertEquals("testTemplateNotTestPlan desc", testTemplate.getDescription());
|
||||
assertNull(testTemplate.getParameters());
|
||||
|
||||
testTemplate = templateMap.get("testTemplate");
|
||||
assertTrue(testTemplate.isTestPlan());
|
||||
assertEquals("testTemplate", testTemplate.getName());
|
||||
assertEquals("/bin/templates/testTemplate.jmx", testTemplate.getFileName());
|
||||
assertEquals("testTemplate desc", testTemplate.getDescription());
|
||||
assertNull(testTemplate.getParameters());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a wrong xml file throws a FileNotFoundException
|
||||
*/
|
||||
@Test(expected = FileNotFoundException.class)
|
||||
public void testInvalidTemplateFile() throws Exception {
|
||||
String xmlTemplatePath = "missing.xml";
|
||||
File templateFile = new File(xmlTemplatePath);
|
||||
TemplateManager templateManager = TemplateManager.getInstance();
|
||||
templateManager.parseTemplateFile(templateFile);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidTemplateXml() throws IOException, SAXException, ParserConfigurationException {
|
||||
try {
|
||||
String xmlTemplatePath = this.getClass().getResource("invalidTemplates.xml").getFile();
|
||||
File templateFile = new File(xmlTemplatePath);
|
||||
TemplateManager templateManager = TemplateManager.getInstance();
|
||||
templateManager.parseTemplateFile(templateFile);
|
||||
} catch (SAXParseException ex) {
|
||||
assertTrue("Exception did not contains expected message, got:"+ex.getMessage(),
|
||||
ex.getMessage().indexOf("Element type \"key\" must be declared.")>=0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -125,6 +125,7 @@ of previous time slot as a base. Starting with this version, each time slot is i
|
|||
<li><pr>411</pr>Use <code>SHA-1</code> instead of <code>SHA1</code> in <code>org.apache.jmeter.save.SaveService</code>. Contributed by Paco (paco.xu at daocloud.io)</li>
|
||||
<li><bug>62914</bug>Add a hint in Thread Group UI about duration of test</li>
|
||||
<li><bug>62925</bug>Add support for ThreadDump to the JMeter non-GUI</li>
|
||||
<li><bug>62870</bug>Templates : Add ability to provide parameters. Contributed by Ubik Load Pack (support at ubikloadpack.com)</li>
|
||||
<li><bug>62829</bug>Allow specifying Proxy server scheme for HTTP request sampler, Advanced tab and command line option. Contributed by Hitesh Patel (hitesh.h.patel at gmail.com)</li>
|
||||
</ul>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
|
||||
license agreements. See the NOTICE file distributed with this work for additional
|
||||
information regarding copyright ownership. The ASF licenses this file to
|
||||
You under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of
|
||||
the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
|
||||
by applicable law or agreed to in writing, software distributed under the
|
||||
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
|
||||
OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License. -->
|
||||
|
||||
<!DOCTYPE document[
|
||||
<!ENTITY hellip "…" >
|
||||
]>
|
||||
|
||||
<document id="$Id$">
|
||||
<properties>
|
||||
<title>User guide: Customizables templates</title>
|
||||
</properties>
|
||||
<body>
|
||||
<section name="Customizable template">
|
||||
<p>
|
||||
This document describes how to create a customizable template.
|
||||
</p>
|
||||
<subsection name="1 Folder structure" anchor="folder_structure">
|
||||
<p>
|
||||
The template feature uses the bin/templates folder which contains :<br/>
|
||||
<ul>
|
||||
<li>templates.xml, the file where you declare the templates you want to be able to use</li>
|
||||
<li>some .jmx and .jmx.fmkr files which are the templates</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>Here is how it looks like:
|
||||
<figure image="templates/template_folder.png">Figure 1 - template folder</figure>
|
||||
</p>
|
||||
</subsection>
|
||||
<subsection name="2 Template declaration" anchor="template_creation">
|
||||
<subsection name="2.1 Basic template declaration" anchor="basic_template_declaration">
|
||||
<p>
|
||||
First of all you must declare your template. To do that, look into the templates.xml file.<br/>
|
||||
This file respect a DTD <br/>
|
||||
Below is the already existing Recording template declaration inside the templates.xml :
|
||||
<figure image="templates/templates_xml.png">Figure 2 - recording template declaration</figure>
|
||||
A template declaration is made as follow :<br/>
|
||||
<ul>
|
||||
<li><code>template</code> element which contains the informations described in the followings tags</li>
|
||||
<li><code>name</code> element which contains the template name the user will see</li>
|
||||
<li><code>fileName</code> element which contains the relative path of the template.</li>
|
||||
<li><code>description</code> element which uses html to describe the template</li>
|
||||
<li><code>optional</code> parameters tag (will be discussed later)</li>
|
||||
</ul>
|
||||
</p>
|
||||
</subsection>
|
||||
<subsection name="2.2 Customizable template declaration" anchor="customizable_template_declaration">
|
||||
<p>
|
||||
Let's say we want the exact same Recording template as in the 2.1 section, but we want to choose the name
|
||||
of the xml file where the recording of view result tree will be saved.<br/><br/>
|
||||
To do so we will use the parameters tag to tell JMeter to ask the user about a name for the concerned file :
|
||||
<figure image="templates/templates_xml_parameters.png">Figure 3 - recording template with parameters</figure>
|
||||
<note>You can put as many parameter tags as you want in the parameters tag.</note>
|
||||
Let's see what changed here.<br/>
|
||||
Firstly, customs templates are <code>.jmx.fmkr</code> files and not only <code>.jmx</code>.<br/><br/>
|
||||
Lastly, we added a <code>parameters</code> tag.<br/> As you can see in the image, a <code>parameters</code> tag contains <code>parameter</code> tags.<br/>
|
||||
Parameter tags are empty and contains 2 attributes :
|
||||
<ul>
|
||||
<li><code>key</code> is the name of the parameter you will ask the user to fill.</li>
|
||||
<li><code>defaultValue</code> is as its name says, the default value the user will see for the parameter.</li>
|
||||
</ul>
|
||||
</p>
|
||||
</subsection>
|
||||
</subsection>
|
||||
<subsection name="3 Template file" anchor="template_file">
|
||||
<subsection name="3.1 Basic template file" anchor="basic_template_file">
|
||||
<p>
|
||||
The template file is the one you used in the fileName tag when you declared your template.
|
||||
A template file is just the saving of a JMeter test plan.
|
||||
</p>
|
||||
</subsection>
|
||||
|
||||
<subsection name="3.2 Customizable template file" anchor="customizable_template_file">
|
||||
<p>
|
||||
In the 2.2 section we saw that a custom template file is a .jmx.fmkr file.<br/>
|
||||
The single difference between them is the .jmx.fmkr will be analyzed by JMeter to<br/>
|
||||
detect customs tag. If a custom tag is found, JMeter will try to replace it by the corresponding
|
||||
given value from the user.<br/><br/>
|
||||
A custom tag is defined as follow :
|
||||
<source><![CDATA[[=<key>]]]></source>
|
||||
This is based on <a href="https://freemarker.apache.org/docs/dgui_misc_alternativesyntax.html#dgui_misc_alternativesyntax_interpolation">Freemarker alternative Interpolation syntax</a>.
|
||||
Let's illustrate how it works with an example.<br/>
|
||||
Consider the following part of the recording.jmx template file : <br/>
|
||||
<figure image="templates/template_recording_filename.png">Figure 4 - recording.jmx save file</figure>
|
||||
The surrounded area correspond to the name of the xml file where the View Results Tree output will be saved.
|
||||
As it is, when you use the template you will always have the same saving filename : <code>recording.xml</code>.<br/><br/>
|
||||
To make it customizable, change your recording template declaration in the templates.xml files by the<br/>
|
||||
one shown in the 2.2 section. Then, rename the recording.jmx file to recording.jmx.fmkr.<br/><br/>
|
||||
When it's done, Change the above selected line by this one :
|
||||
<source><![CDATA[<stringProp name="filename">[=xmlFileName]</stringProp>]]></source>
|
||||
<br/>
|
||||
It's over ! With this configuration, if you chose to use the recording template, JMeter will ask you
|
||||
a xmlFileName (correspond to the key value in the declaration).
|
||||
<figure image="templates/template_parameters_window.png">Figure 5 - JMeter asks you the value you want to put for the key</figure>
|
||||
Then, you will find the expected value in the created template where you placed the [=xmlFileName] tag :
|
||||
<figure image="templates/template_recording_retrieved_value.png">Figure 6 - the value changed</figure>
|
||||
</p>
|
||||
</subsection>
|
||||
</subsection>
|
||||
</section>
|
||||
</body>
|
||||
</document>
|
||||
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1015 B |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 12 KiB |