/*
* 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.testbeans.gui;
import java.awt.BorderLayout;
import java.awt.Component;
import java.beans.BeanDescriptor;
import java.beans.BeanInfo;
import java.beans.Customizer;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.beans.PropertyEditorManager;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.swing.JPopupMenu;
import org.apache.commons.collections.map.LRUMap;
import org.apache.jmeter.assertions.Assertion;
import org.apache.jmeter.assertions.gui.AbstractAssertionGui;
import org.apache.jmeter.config.ConfigElement;
import org.apache.jmeter.config.ConfigTestElement;
import org.apache.jmeter.config.gui.AbstractConfigGui;
import org.apache.jmeter.control.Controller;
import org.apache.jmeter.control.gui.AbstractControllerGui;
import org.apache.jmeter.gui.AbstractJMeterGuiComponent;
import org.apache.jmeter.gui.JMeterGUIComponent;
import org.apache.jmeter.gui.util.MenuFactory;
import org.apache.jmeter.processor.PostProcessor;
import org.apache.jmeter.processor.PreProcessor;
import org.apache.jmeter.processor.gui.AbstractPostProcessorGui;
import org.apache.jmeter.processor.gui.AbstractPreProcessorGui;
import org.apache.jmeter.reporters.AbstractListenerElement;
import org.apache.jmeter.samplers.Sampler;
import org.apache.jmeter.samplers.gui.AbstractSamplerGui;
import org.apache.jmeter.testbeans.BeanInfoSupport;
import org.apache.jmeter.testbeans.TestBean;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.property.AbstractProperty;
import org.apache.jmeter.testelement.property.JMeterProperty;
import org.apache.jmeter.testelement.property.PropertyIterator;
import org.apache.jmeter.timers.Timer;
import org.apache.jmeter.timers.gui.AbstractTimerGui;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.visualizers.Visualizer;
import org.apache.jmeter.visualizers.gui.AbstractVisualizer;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.jorphan.util.JOrphanUtils;
import org.apache.log.Logger;
/**
* JMeter GUI element editing for TestBean elements.
* <p>
* The actual GUI is always a bean customizer: if the bean descriptor provides
* one, it will be used; otherwise, a GenericTestBeanCustomizer will be created
* for this purpose.
* <p>
* Those customizers deviate from the standards only in that, instead of a bean,
* they will receive a Map in the setObject call. This will be a property name
* to value Map. The customizer is also in charge of initializing empty Maps
* with sensible initial values.
* <p>
* If the provided Customizer class implements the SharedCustomizer interface,
* the same instance of the customizer will be reused for all beans of the type:
* setObject(map) can then be called multiple times. Otherwise, one separate
* instance will be used for each element. For efficiency reasons, most
* customizers should implement SharedCustomizer.
*
*/
public class TestBeanGUI extends AbstractJMeterGuiComponent implements JMeterGUIComponent {
private static final Logger log = LoggingManager.getLoggerForClass();
private Class testBeanClass;
private transient BeanInfo beanInfo;
private Class customizerClass;
/**
* The single customizer if the customizer class implements
* SharedCustomizer, null otherwise.
*/
private Customizer customizer = null;
/**
* TestElement to Customizer map if customizer is null. This is necessary to
* avoid the cost of creating a new customizer on each edit. The cache size
* needs to be limited, though, to avoid memory issues when editing very
* large test plans.
*/
private Map customizers = new LRUMap(20);
/**
* Index of the customizer in the JPanel's child component list:
*/
private int customizerIndexInPanel;
/**
* The property name to value map that the active customizer edits:
*/
private Map propertyMap = new HashMap();
/**
* Whether the GUI components have been created.
*/
private boolean initialized = false;
static {
List paths = new LinkedList();
paths.add("org.apache.jmeter.testbeans.gui");// $NON-NLS-1$
paths.addAll(Arrays.asList(PropertyEditorManager.getEditorSearchPath()));
String s = JMeterUtils.getPropDefault("propertyEditorSearchPath", null);// $NON-NLS-1$
if (s != null) {
paths.addAll(Arrays.asList(JOrphanUtils.split(s, ",", "")));// $NON-NLS-1$ // $NON-NLS-2$
}
PropertyEditorManager.setEditorSearchPath((String[]) paths.toArray(new String[0]));
}
// Dummy for JUnit test
public TestBeanGUI() {
log.warn("Constructor only for use in testing");// $NON-NLS-1$
}
public TestBeanGUI(Class testBeanClass) {
super();
log.debug("testing class: " + testBeanClass.getName());
// A quick verification, just in case:
if (!TestBean.class.isAssignableFrom(testBeanClass)) {
Error e = new Error();
log.error("This should never happen!", e);
throw e; // Programming error: bail out.
}
this.testBeanClass = testBeanClass;
// Get the beanInfo:
try {
beanInfo = Introspector.getBeanInfo(testBeanClass);
} catch (IntrospectionException e) {
log.error("Can't get beanInfo for " + testBeanClass.getName(), e);
throw new Error(e.toString()); // Programming error. Don't
// continue.
}
customizerClass = beanInfo.getBeanDescriptor().getCustomizerClass();
// Creation of the customizer and GUI initialization is delayed until
// the
// first
// configure call. We don't need all that just to find out the static
// label, menu
// categories, etc!
initialized = false;
}
private Customizer createCustomizer() {
try {
return (Customizer) customizerClass.newInstance();
} catch (InstantiationException e) {
log.error("Could not instantiate customizer of class " + customizerClass, e);
throw new Error(e.toString());
} catch (IllegalAccessException e) {
log.error("Could not instantiate customizer of class " + customizerClass, e);
throw new Error(e.toString());
}
}
/*
* (non-Javadoc)
*
* @see org.apache.jmeter.gui.JMeterGUIComponent#getStaticLabel()
*/
public String getStaticLabel() {
if (beanInfo == null){
return "null";// $NON-NLS-1$
}
return beanInfo.getBeanDescriptor().getDisplayName();
}
/*
* (non-Javadoc)
*
* @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement()
*/
public TestElement createTestElement() {
try {
TestElement element = (TestElement) testBeanClass.newInstance();
// configure(element);
// super.clear(); // set name, enabled.
modifyTestElement(element); // put the default values back into the
// new element
return element;
} catch (InstantiationException e) {
log.error("Can't create test element", e);
throw new Error(e.toString()); // Programming error. Don't
// continue.
} catch (IllegalAccessException e) {
log.error("Can't create test element", e);
throw new Error(e.toString()); // Programming error. Don't
// continue.
}
}
/*
* (non-Javadoc)
*
* @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(org.apache.jmeter.testelement.TestElement)
*/
public void modifyTestElement(TestElement element) {
// Fetch data from screen fields
if (customizer instanceof GenericTestBeanCustomizer) {
GenericTestBeanCustomizer gtbc = (GenericTestBeanCustomizer) customizer;
gtbc.saveGuiFields();
}
configureTestElement(element);
// Copy all property values from the map into the element:
PropertyDescriptor[] props = beanInfo.getPropertyDescriptors();
for (int i = 0; i < props.length; i++) {
String name = props[i].getName();
Object value = propertyMap.get(name);
log.debug("Modify " + name + " to " + value);
if (value == null) {
Object valueNotUnDefined = props[i].getValue(BeanInfoSupport.NOT_UNDEFINED);
if (valueNotUnDefined != null && ((Boolean) valueNotUnDefined).booleanValue()) {
setPropertyInElement(element, name, props[i].getValue(BeanInfoSupport.DEFAULT));
} else {
element.removeProperty(name);
}
} else {
setPropertyInElement(element, name, propertyMap.get(name));
}
}
}
/**
* @param element
* @param name
*/
private void setPropertyInElement(TestElement element, String name, Object value) {
JMeterProperty jprop = AbstractProperty.createProperty(value);
jprop.setName(name);
element.setProperty(jprop);
}
/*
* (non-Javadoc)
*
* @see org.apache.jmeter.gui.JMeterGUIComponent#createPopupMenu()
*/
public JPopupMenu createPopupMenu() {
// TODO: this menu is too wide (allows, e.g. to add controllers, no
// matter what the type of the element).
// Change to match the actual bean's capabilities.
if (Timer.class.isAssignableFrom(testBeanClass))// HACK: Fix one such problem
{
return MenuFactory.getDefaultTimerMenu();
}
else if(Sampler.class.isAssignableFrom(testBeanClass))
{
return MenuFactory.getDefaultSamplerMenu();
}
else if(ConfigTestElement.class.isAssignableFrom(testBeanClass))
{
return MenuFactory.getDefaultConfigElementMenu();
}
else if(Assertion.class.isAssignableFrom(testBeanClass))
{
return MenuFactory.getDefaultAssertionMenu();
}
else if(PostProcessor.class.isAssignableFrom(testBeanClass) ||
PreProcessor.class.isAssignableFrom(testBeanClass))
{
return MenuFactory.getDefaultExtractorMenu();
}
else if(AbstractListenerElement.class.isAssignableFrom(testBeanClass))
{
return MenuFactory.getDefaultVisualizerMenu();
}
else {
return MenuFactory.getDefaultControllerMenu();
}
}
/*
* (non-Javadoc)
*
* @see org.apache.jmeter.gui.JMeterGUIComponent#configure(org.apache.jmeter.testelement.TestElement)
*/
public void configure(TestElement element) {
if (!initialized){
init();
}
clearGui();
super.configure(element);
// Copy all property values into the map:
for (PropertyIterator jprops = element.propertyIterator(); jprops.hasNext();) {
JMeterProperty jprop = jprops.next();
propertyMap.put(jprop.getName(), jprop.getObjectValue());
}
if (customizer != null) {
customizer.setObject(propertyMap);
} else {
if (initialized){
remove(customizerIndexInPanel);
}
Customizer c = (Customizer) customizers.get(element);
if (c == null) {
c = createCustomizer();
c.setObject(propertyMap);
customizers.put(element, c);
}
add((Component) c, BorderLayout.CENTER);
}
initialized = true;
}
/*
* (non-Javadoc)
*
* @see org.apache.jmeter.gui.JMeterGUIComponent#getMenuCategories()
*/
public Collection getMenuCategories() {
List menuCategories = new LinkedList();
BeanDescriptor bd = beanInfo.getBeanDescriptor();
// We don't want to show expert beans in the menus unless we're
// in expert mode:
if (bd.isExpert() && !JMeterUtils.isExpertMode()) {
return null;
}
int matches = 0; // How many classes can we assign from?
// TODO: there must be a nicer way...
if (Assertion.class.isAssignableFrom(testBeanClass)) {
menuCategories.add(MenuFactory.ASSERTIONS);
bd.setValue(TestElement.GUI_CLASS, AbstractAssertionGui.class.getName());
matches++;
}
if (ConfigElement.class.isAssignableFrom(testBeanClass)) {
menuCategories.add(MenuFactory.CONFIG_ELEMENTS);
bd.setValue(TestElement.GUI_CLASS, AbstractConfigGui.class.getName());
matches++;
}
if (Controller.class.isAssignableFrom(testBeanClass)) {
menuCategories.add(MenuFactory.CONTROLLERS);
bd.setValue(TestElement.GUI_CLASS, AbstractControllerGui.class.getName());
matches++;
}
if (Visualizer.class.isAssignableFrom(testBeanClass)) {
menuCategories.add(MenuFactory.LISTENERS);
bd.setValue(TestElement.GUI_CLASS, AbstractVisualizer.class.getName());
matches++;
}
if (PostProcessor.class.isAssignableFrom(testBeanClass)) {
menuCategories.add(MenuFactory.POST_PROCESSORS);
bd.setValue(TestElement.GUI_CLASS, AbstractPostProcessorGui.class.getName());
matches++;
}
if (PreProcessor.class.isAssignableFrom(testBeanClass)) {
matches++;
menuCategories.add(MenuFactory.PRE_PROCESSORS);
bd.setValue(TestElement.GUI_CLASS, AbstractPreProcessorGui.class.getName());
}
if (Sampler.class.isAssignableFrom(testBeanClass)) {
matches++;
menuCategories.add(MenuFactory.SAMPLERS);
bd.setValue(TestElement.GUI_CLASS, AbstractSamplerGui.class.getName());
}
if (Timer.class.isAssignableFrom(testBeanClass)) {
matches++;
menuCategories.add(MenuFactory.TIMERS);
bd.setValue(TestElement.GUI_CLASS, AbstractTimerGui.class.getName());
}
if (matches == 0) {
log.error("Could not assign GUI class to " + testBeanClass.getName());
} else if (matches > 1) {// may be impossible, but no harm in
// checking ...
log.error("More than 1 GUI class found for " + testBeanClass.getName());
}
return menuCategories;
}
private void init() {
setLayout(new BorderLayout(0, 5));
setBorder(makeBorder());
add(makeTitlePanel(), BorderLayout.NORTH);
customizerIndexInPanel = getComponentCount();
if (customizerClass == null) {
customizer = new GenericTestBeanCustomizer(beanInfo);
} else if (SharedCustomizer.class.isAssignableFrom(customizerClass)) {
customizer = createCustomizer();
}
if (customizer != null){
add((Component) customizer, BorderLayout.CENTER);
}
}
/*
* (non-Javadoc)
*
* @see org.apache.jmeter.gui.JMeterGUIComponent#getLabelResource()
*/
public String getLabelResource() {
// @see getStaticLabel
return null;
}
/*
* (non-Javadoc)
*
* @see org.apache.jmeter.gui.JMeterGUIComponent#clearGui()
*/
public void clearGui() {
super.clearGui();
if (customizer instanceof GenericTestBeanCustomizer) {
GenericTestBeanCustomizer gtbc = (GenericTestBeanCustomizer) customizer;
gtbc.clearGuiFields();
}
}
}