blob: ef659afab1d49fabfaea254e856489ee6503b90a [file] [log] [blame]
/*
* 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.functions.gui;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.jmeter.config.Argument;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.config.gui.ArgumentsPanel;
import org.apache.jmeter.engine.util.CompoundVariable;
import org.apache.jmeter.functions.Function;
import org.apache.jmeter.gui.action.ActionNames;
import org.apache.jmeter.gui.action.ActionRouter;
import org.apache.jmeter.gui.action.Help;
import org.apache.jmeter.gui.action.KeyStrokes;
import org.apache.jmeter.gui.util.JSyntaxTextArea;
import org.apache.jmeter.gui.util.JTextScrollPane;
import org.apache.jmeter.testelement.property.PropertyIterator;
import org.apache.jmeter.threads.JMeterContext;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.threads.JMeterThread;
import org.apache.jmeter.threads.JMeterVariables;
import org.apache.jmeter.threads.ThreadGroup;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.util.LocaleChangeEvent;
import org.apache.jmeter.util.LocaleChangeListener;
import org.apache.jorphan.documentation.VisibleForTesting;
import org.apache.jorphan.gui.ComponentUtil;
import org.apache.jorphan.gui.GuiUtils;
import org.apache.jorphan.gui.JLabeledChoice;
import org.apache.jorphan.gui.JLabeledTextField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.miginfocom.swing.MigLayout;
public class FunctionHelper extends JDialog implements ActionListener, ChangeListener, LocaleChangeListener {
private static final long serialVersionUID = 240L;
private static final Logger log = LoggerFactory.getLogger(FunctionHelper.class);
private static final String GENERATE = "GENERATE";
private static final String RESET_VARS = "RESET_VARS";
private static final String FUNCTION_PREFIX = "__";
private JLabeledChoice functionList;
private ArgumentsPanel parameterPanel;
private JLabeledTextField cutPasteFunction;
private JSyntaxTextArea resultTextArea;
private JSyntaxTextArea variablesTextArea;
private JMeterVariables jMeterVariables = new JMeterVariables();
@VisibleForTesting
public FunctionHelper() {
super();
}
public FunctionHelper(JFrame parent) {
super(parent, JMeterUtils.getResString("function_helper_title"), false); //$NON-NLS-1$
init();
JMeterUtils.addLocaleChangeListener(this);
}
/**
* Allow Dialog to be closed by ESC key
*/
@Override
protected JRootPane createRootPane() {
JRootPane rootPane = new JRootPane();
javax.swing.Action escapeAction = new AbstractAction("ESCAPE") {
private static final long serialVersionUID = -4036804004190858925L;
@Override
public void actionPerformed(ActionEvent actionEvent) {
setVisible(false);
}
};
rootPane.getActionMap().put(escapeAction.getValue(Action.NAME), escapeAction);
InputMap inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
inputMap.put(KeyStrokes.ESC, escapeAction.getValue(Action.NAME));
return rootPane;
}
private void init() { // WARNING: called from ctor so must not be overridden (i.e. must be private or final)
parameterPanel = new ArgumentsPanel(true,JMeterUtils.getResString("function_params")); //$NON-NLS-1$
initializeFunctionList();
this.getContentPane().setLayout(new BorderLayout(10, 10));
JPanel comboPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
comboPanel.add(functionList);
JButton helpButton = new JButton(JMeterUtils.getResString("help")); //$NON-NLS-1$
helpButton.addActionListener(new HelpListener());
comboPanel.add(helpButton);
this.getContentPane().add(comboPanel, BorderLayout.NORTH);
this.getContentPane().add(parameterPanel, BorderLayout.CENTER);
JPanel resultsPanel = new JPanel(new MigLayout("fillx, wrap 2", "[][fill,grow]"));
JPanel generatePanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
cutPasteFunction = new JLabeledTextField(JMeterUtils.getResString("cut_paste_function"), 35, null, false); //$NON-NLS-1$
cutPasteFunction.setEnabled(false);
generatePanel.add(cutPasteFunction);
JButton generateButton = new JButton(JMeterUtils.getResString("generate")); //$NON-NLS-1$
generateButton.setActionCommand(GENERATE);
generateButton.addActionListener(this);
generatePanel.add(generateButton);
JButton resetVarsButton = new JButton(JMeterUtils.getResString("function_helper_reset_vars")); //$NON-NLS-1$
resetVarsButton.setActionCommand(RESET_VARS);
resetVarsButton.addActionListener(this);
generatePanel.add(resetVarsButton);
resultTextArea = JSyntaxTextArea.getInstance(5,60);
resultTextArea.setEditable(false);
resultTextArea.setToolTipText(JMeterUtils.getResString("function_helper_dialog_result_warn"));
variablesTextArea = JSyntaxTextArea.getInstance(10,60);
variablesTextArea.setEditable(false);
resultsPanel.add(generatePanel, "span 2");
JTextScrollPane resultTextAreaJSP = JTextScrollPane.getInstance(resultTextArea);
resultsPanel.add(JMeterUtils.labelFor(resultTextAreaJSP, "result_function"));
resultsPanel.add(resultTextAreaJSP);
JTextScrollPane variablesTextAreaJSP = JTextScrollPane.getInstance(variablesTextArea);
resultsPanel.add(JMeterUtils.labelFor(variablesTextAreaJSP, "function_helper_dialog_variables"));
resultsPanel.add(variablesTextAreaJSP);
this.getContentPane().add(resultsPanel, BorderLayout.SOUTH);
this.pack();
ComponentUtil.centerComponentInWindow(this);
}
private void initializeFunctionList() {
String[] functionNames = CompoundVariable.getFunctionNames();
Arrays.sort(functionNames, String::compareToIgnoreCase);
functionList = new JLabeledChoice(JMeterUtils.getResString("choose_function"), //$NON-NLS-1$
Arrays.stream(functionNames).map(
e -> e.substring(FUNCTION_PREFIX.length())).collect(Collectors.toList()).toArray(new String[0]));
functionList.addChangeListener(this);
}
@Override
public void stateChanged(ChangeEvent event) {
try {
initParameterPanel();
getContentPane().remove(parameterPanel);
this.pack();
getContentPane().add(parameterPanel, BorderLayout.CENTER);
this.pack();
this.validate();
resultTextArea.setText("");
this.repaint();
} catch (InstantiationException | IllegalAccessException ex) {
log.info("Exception during stateChanged", ex);
}
}
/**
* @throws InstantiationException if function instantiation fails
* @throws IllegalAccessException if function instantiation fails
*/
protected void initParameterPanel() throws InstantiationException, IllegalAccessException {
Arguments args = new Arguments();
Function function;
String functionName = getFunctionName(functionList.getText());
try {
function = CompoundVariable.getFunctionClass(functionName)
.getDeclaredConstructor()
.newInstance();
} catch (InvocationTargetException | NoSuchMethodException e) {
InstantiationException ex = new InstantiationException("Unable to instantiate " + functionName);
ex.initCause(e instanceof InvocationTargetException ? e.getCause() : e);
throw ex;
}
List<String> argumentDesc = function.getArgumentDesc();
for (String help : argumentDesc) {
args.addArgument(help, ""); //$NON-NLS-1$
}
parameterPanel.configure(args);
parameterPanel.revalidate();
}
/**
* @param text Function name without __ prefix
* @return String function name with __ prefix
*/
private static final String getFunctionName(String text) {
return FUNCTION_PREFIX+text;
}
@Override
public void actionPerformed(ActionEvent e) {
String actionCommand = e.getActionCommand();
if(GENERATE.equals(actionCommand)) {
String functionName = getFunctionName(functionList.getText());
Arguments args = (Arguments) parameterPanel.createTestElement();
String functionCall = buildFunctionCallString(functionName, args);
cutPasteFunction.setText(functionCall);
cutPasteFunction.setEnabled(true);
GuiUtils.copyTextToClipboard(cutPasteFunction.getText());
CompoundVariable function = new CompoundVariable(functionCall);
JMeterContext threadContext = JMeterContextService.getContext();
threadContext.setVariables(jMeterVariables);
threadContext.setThreadNum(1);
threadContext.getVariables().put(JMeterThread.LAST_SAMPLE_OK, "true");
ThreadGroup threadGroup = new ThreadGroup();
threadGroup.setName("FunctionHelper-Dialog-ThreadGroup");
threadContext.setThreadGroup(threadGroup);
try {
resultTextArea.setText(function.execute().trim());
} catch(Exception ex) {
log.error("Error calling function {}", functionCall, ex);
resultTextArea.setText(ex.getMessage() + ", \nstacktrace:\n "+
ExceptionUtils.getStackTrace(ex));
resultTextArea.setCaretPosition(0);
}
variablesTextArea.setText(variablesToString(jMeterVariables));
} else {
jMeterVariables = new JMeterVariables();
variablesTextArea.setText(variablesToString(jMeterVariables));
}
}
private static String variablesToString(JMeterVariables jMeterVariables) {
StringBuilder sb = new StringBuilder();
jMeterVariables.entrySet().forEach(e->sb.append(e.getKey()).append("=").append(e.getValue()).append("\r\n"));
return sb.toString();
}
@VisibleForTesting
static String buildFunctionCallString(String functionName, Arguments args) {
StringBuilder functionCall = new StringBuilder("${");
functionCall.append(functionName);
if (args.getArguments().size() > 0) {
functionCall.append("(");
PropertyIterator iter = args.iterator();
boolean first = true;
while (iter.hasNext()) {
Argument arg = (Argument) iter.next().getObjectValue();
if (!first) {
functionCall.append(",");
}
functionCall.append(escapeCommata(arg.getValue()));
first = false;
}
functionCall.append(")");
}
functionCall.append("}");
return functionCall.toString();
}
private static final char ANY_NORMAL_CHAR = ' ';
/**
* Escape commata that are in the argument but "outside" of variable replacement structures.
*
* @param arg string that should be escaped
* @return escaped string
*/
private static String escapeCommata(String arg) {
int level = 0;
StringBuilder result = new StringBuilder(arg.length());
try (Reader r = new StringReader(arg)) {
int c;
char lastChar = ANY_NORMAL_CHAR;
while ((c = r.read()) != -1) {
char nextChar = (char) c;
if (lastChar == '\\') {
lastChar = ANY_NORMAL_CHAR;
} else if (lastChar == '$' && nextChar == '{') {
level++;
lastChar = ANY_NORMAL_CHAR;
} else if (nextChar == '}') {
level--;
lastChar = ANY_NORMAL_CHAR;
} else if (nextChar == ',' && level == 0) {
result.append('\\');
lastChar = ANY_NORMAL_CHAR;
} else {
lastChar = nextChar;
}
result.append(nextChar);
}
} catch (IOException e) {
log.warn("Can't escape commata in input string: {}", arg, e);
return arg;
}
return result.toString();
}
private class HelpListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
String[] source = new String[] { Help.HELP_FUNCTIONS, getFunctionName(functionList.getText()) };
ActionRouter.getInstance().doActionNow(
new ActionEvent(source, e.getID(), ActionNames.HELP));
}
}
@Override
public void localeChanged(LocaleChangeEvent event) {
setTitle(JMeterUtils.getResString("function_helper_title")); //$NON-NLS-1$
this.getContentPane().removeAll(); // so we can add them again in init
init();
}
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
if (visible) {
try {
initParameterPanel();
} catch (InstantiationException | IllegalAccessException ex) {
log.error("Error initializing parameter panel", ex);
}
}
}
}