/*
* 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.visualizers;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.swing.Box;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import org.apache.jmeter.config.ConfigTestElement;
import org.apache.jmeter.config.gui.AbstractConfigGui;
import org.apache.jmeter.control.GenericController;
import org.apache.jmeter.control.gui.LogicControllerGui;
import org.apache.jmeter.exceptions.IllegalUserActionException;
import org.apache.jmeter.gui.GuiPackage;
import org.apache.jmeter.gui.UnsharedComponent;
import org.apache.jmeter.gui.tree.JMeterTreeModel;
import org.apache.jmeter.gui.tree.JMeterTreeNode;
import org.apache.jmeter.gui.util.MenuFactory;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.util.JMeterUtils;
/**
* Workbench test element to create a test plan containing samples of each test element
* (apart from Threads and Test Fragment).
*<p>
* The user creates a Thread Group, and the elements are created as child elements of
* Simple Controllers.
*<p>
* Note: the code currently runs on all versions of JMeter back to 2.2.
* Beware of making changes that rely on more recent APIs.
*/
public class GenerateTreeGui extends AbstractConfigGui implements
ActionListener, UnsharedComponent {
private static final long serialVersionUID = 1L;
private JButton generateButton = new JButton("Generate");
public GenerateTreeGui() {
super();
init();
}
@Override
public String getLabelResource() {
return "test_plan"; // $NON-NLS-1$
}
@Override
public String getStaticLabel() {
return "Test Generator"; // $NON-NLS-1$
}
@Override
public String getDocAnchor() {
return super.getDocAnchor();
}
@Override
public Collection<String> getMenuCategories() {
return Arrays.asList(new String[] { MenuFactory.NON_TEST_ELEMENTS });
}
@Override
public void actionPerformed(ActionEvent action) {
GuiPackage guiPackage = GuiPackage.getInstance();
JMeterTreeModel treeModel = guiPackage.getTreeModel();
JMeterTreeNode myTarget = findFirstNodeOfType(org.apache.jmeter.threads.ThreadGroup.class, treeModel);
if (myTarget == null) {
JMeterUtils.reportErrorToUser("Cannot find Thread Group");
return;
}
addElements(MenuFactory.CONTROLLERS, "Controllers", guiPackage, treeModel, myTarget);
addElements(MenuFactory.CONFIG_ELEMENTS, "Config Elements", guiPackage, treeModel, myTarget);
addElements(MenuFactory.TIMERS, "Timers", guiPackage, treeModel, myTarget);
addElements(MenuFactory.PRE_PROCESSORS, "Pre Processors", guiPackage, treeModel, myTarget);
addElements(MenuFactory.SAMPLERS, "Samplers", guiPackage, treeModel, myTarget);
addElements(MenuFactory.POST_PROCESSORS, "Post Processors", guiPackage, treeModel, myTarget);
addElements(MenuFactory.ASSERTIONS, "Assertions", guiPackage, treeModel, myTarget);
addElements(MenuFactory.LISTENERS, "Listeners", guiPackage, treeModel, myTarget);
}
private void addElements(String menuKey, String title, GuiPackage guiPackage, JMeterTreeModel treeModel,
JMeterTreeNode myTarget) {
myTarget = addSimpleController(treeModel, myTarget, title);
JPopupMenu jp = MenuFactory.makeMenu(menuKey, "").getPopupMenu();
for (Component comp : jp.getComponents()) {
JMenuItem jmi = (JMenuItem) comp;
try {
TestElement testElement = guiPackage.createTestElement(jmi.getName());
addToTree(treeModel, myTarget, testElement);
} catch (Exception e) {
addSimpleController(treeModel, myTarget, jmi.getName()+" "+e.getMessage());
}
}
}
@Override
public TestElement createTestElement() {
TestElement el = new ConfigTestElement();
modifyTestElement(el);
return el;
}
@Override
public void modifyTestElement(TestElement element) {
configureTestElement(element);
}
/**
* Create a panel containing the title label for the table.
*
* @return a panel containing the title label
*/
private Component makeLabelPanel() {
JPanel labelPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
ButtonGroup bg = new ButtonGroup();
bg.add(generateButton);
generateButton.addActionListener(this);
labelPanel.add(generateButton);
return labelPanel;
}
/**
* Initialize the components and layout of this component.
*/
private void init() {
setLayout(new BorderLayout(0, 5));
setBorder(makeBorder());
add(makeTitlePanel(), BorderLayout.NORTH);
JPanel p = new JPanel();
p.setLayout(new BorderLayout());
p.add(makeLabelPanel(), BorderLayout.NORTH);
// Force a minimum table height of 70 pixels
p.add(Box.createVerticalStrut(70), BorderLayout.WEST);
add(p, BorderLayout.CENTER);
}
/**
* Helper method to add a Simple Controller to contain the elements.
* Called from Application Thread that needs to update GUI (JMeterTreeModel)
* @param model
* Test component tree model
* @param node
* Node in the tree where we will add the Controller
* @param name
* A name for the Controller
* @return the new node
*/
private JMeterTreeNode addSimpleController(JMeterTreeModel model, JMeterTreeNode node, String name) {
final TestElement sc = new GenericController();
sc.setProperty(TestElement.GUI_CLASS, LOGIC_CONTROLLER_GUI);
sc.setProperty(TestElement.NAME, name); // Use old style
return addToTree(model, node, sc);
}
private static class RunGUI implements Runnable {
private final JMeterTreeModel model;
private final JMeterTreeNode node;
private final TestElement testElement;
RunGUI(JMeterTreeModel model, JMeterTreeNode node, TestElement testElement) {
super();
this.model = model;
this.node = node;
this.testElement = testElement;
}
volatile JMeterTreeNode newNode;
@Override
public void run() {
try {
newNode = model.addComponent(testElement, node);
} catch (IllegalUserActionException e) {
throw new Error(e);
}
}
}
private JMeterTreeNode addToTree(final JMeterTreeModel model,
final JMeterTreeNode node, final TestElement sc) {
RunGUI runnable = new RunGUI(model, node, sc);
if(SwingUtilities.isEventDispatchThread()) {
runnable.run();
} else {
try {
SwingUtilities.invokeAndWait(runnable);
} catch (InterruptedException | InvocationTargetException e) {
throw new Error(e);
}
}
return runnable.newNode;
}
private static final String LOGIC_CONTROLLER_GUI = LogicControllerGui.class.getName();
/**
* Finds the first enabled node of a given type in the tree.
*
* @param type
* class of the node to be found
* @param treeModel the tree to search in
*
* @return the first node of the given type in the test component tree, or
* <code>null</code> if none was found.
*/
private JMeterTreeNode findFirstNodeOfType(Class<?> type, JMeterTreeModel treeModel) {
List<JMeterTreeNode> nodes = treeModel.getNodesOfType(type);
for (JMeterTreeNode node : nodes) {
if (node.isEnabled()) {
return node;
}
}
return null;
}
}