001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
013: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
014: * License for the specific language governing permissions and limitations
015: * under the License.
016: */
017: package org.apache.jmeter.testbeans.gui;
018:
019: import java.awt.BorderLayout;
020: import java.awt.Component;
021: import java.beans.BeanDescriptor;
022: import java.beans.BeanInfo;
023: import java.beans.Customizer;
024: import java.beans.IntrospectionException;
025: import java.beans.Introspector;
026: import java.beans.PropertyDescriptor;
027: import java.beans.PropertyEditorManager;
028: import java.util.Arrays;
029: import java.util.Collection;
030: import java.util.HashMap;
031: import java.util.LinkedList;
032: import java.util.List;
033: import java.util.Map;
034:
035: import javax.swing.JPopupMenu;
036:
037: import org.apache.commons.collections.map.LRUMap;
038: import org.apache.jmeter.assertions.Assertion;
039: import org.apache.jmeter.assertions.gui.AbstractAssertionGui;
040: import org.apache.jmeter.config.ConfigElement;
041: import org.apache.jmeter.config.ConfigTestElement;
042: import org.apache.jmeter.config.gui.AbstractConfigGui;
043: import org.apache.jmeter.control.Controller;
044: import org.apache.jmeter.control.gui.AbstractControllerGui;
045: import org.apache.jmeter.gui.AbstractJMeterGuiComponent;
046: import org.apache.jmeter.gui.JMeterGUIComponent;
047: import org.apache.jmeter.gui.util.MenuFactory;
048: import org.apache.jmeter.processor.PostProcessor;
049: import org.apache.jmeter.processor.PreProcessor;
050: import org.apache.jmeter.processor.gui.AbstractPostProcessorGui;
051: import org.apache.jmeter.processor.gui.AbstractPreProcessorGui;
052: import org.apache.jmeter.reporters.AbstractListenerElement;
053: import org.apache.jmeter.samplers.Sampler;
054: import org.apache.jmeter.samplers.gui.AbstractSamplerGui;
055: import org.apache.jmeter.testbeans.BeanInfoSupport;
056: import org.apache.jmeter.testbeans.TestBean;
057: import org.apache.jmeter.testelement.TestElement;
058: import org.apache.jmeter.testelement.property.AbstractProperty;
059: import org.apache.jmeter.testelement.property.JMeterProperty;
060: import org.apache.jmeter.testelement.property.PropertyIterator;
061: import org.apache.jmeter.timers.Timer;
062: import org.apache.jmeter.timers.gui.AbstractTimerGui;
063: import org.apache.jmeter.util.JMeterUtils;
064: import org.apache.jmeter.visualizers.Visualizer;
065: import org.apache.jmeter.visualizers.gui.AbstractVisualizer;
066: import org.apache.jorphan.logging.LoggingManager;
067: import org.apache.jorphan.util.JOrphanUtils;
068: import org.apache.log.Logger;
069:
070: /**
071: * JMeter GUI element editing for TestBean elements.
072: * <p>
073: * The actual GUI is always a bean customizer: if the bean descriptor provides
074: * one, it will be used; otherwise, a GenericTestBeanCustomizer will be created
075: * for this purpose.
076: * <p>
077: * Those customizers deviate from the standards only in that, instead of a bean,
078: * they will receive a Map in the setObject call. This will be a property name
079: * to value Map. The customizer is also in charge of initializing empty Maps
080: * with sensible initial values.
081: * <p>
082: * If the provided Customizer class implements the SharedCustomizer interface,
083: * the same instance of the customizer will be reused for all beans of the type:
084: * setObject(map) can then be called multiple times. Otherwise, one separate
085: * instance will be used for each element. For efficiency reasons, most
086: * customizers should implement SharedCustomizer.
087: *
088: */
089: public class TestBeanGUI extends AbstractJMeterGuiComponent implements
090: JMeterGUIComponent {
091: private static final Logger log = LoggingManager
092: .getLoggerForClass();
093:
094: private Class testBeanClass;
095:
096: private transient BeanInfo beanInfo;
097:
098: private Class customizerClass;
099:
100: /**
101: * The single customizer if the customizer class implements
102: * SharedCustomizer, null otherwise.
103: */
104: private Customizer customizer = null;
105:
106: /**
107: * TestElement to Customizer map if customizer is null. This is necessary to
108: * avoid the cost of creating a new customizer on each edit. The cache size
109: * needs to be limited, though, to avoid memory issues when editing very
110: * large test plans.
111: */
112: private Map customizers = new LRUMap(20);
113:
114: /**
115: * Index of the customizer in the JPanel's child component list:
116: */
117: private int customizerIndexInPanel;
118:
119: /**
120: * The property name to value map that the active customizer edits:
121: */
122: private Map propertyMap = new HashMap();
123:
124: /**
125: * Whether the GUI components have been created.
126: */
127: private boolean initialized = false;
128:
129: static {
130: List paths = new LinkedList();
131: paths.add("org.apache.jmeter.testbeans.gui");// $NON-NLS-1$
132: paths.addAll(Arrays.asList(PropertyEditorManager
133: .getEditorSearchPath()));
134: String s = JMeterUtils.getPropDefault(
135: "propertyEditorSearchPath", null);// $NON-NLS-1$
136: if (s != null) {
137: paths.addAll(Arrays.asList(JOrphanUtils.split(s, ",", "")));// $NON-NLS-1$ // $NON-NLS-2$
138: }
139: PropertyEditorManager.setEditorSearchPath((String[]) paths
140: .toArray(new String[0]));
141: }
142:
143: // Dummy for JUnit test
144: public TestBeanGUI() {
145: log.warn("Constructor only for use in testing");// $NON-NLS-1$
146: }
147:
148: public TestBeanGUI(Class testBeanClass) {
149: super ();
150: log.debug("testing class: " + testBeanClass.getName());
151: // A quick verification, just in case:
152: if (!TestBean.class.isAssignableFrom(testBeanClass)) {
153: Error e = new Error();
154: log.error("This should never happen!", e);
155: throw e; // Programming error: bail out.
156: }
157:
158: this .testBeanClass = testBeanClass;
159:
160: // Get the beanInfo:
161: try {
162: beanInfo = Introspector.getBeanInfo(testBeanClass);
163: } catch (IntrospectionException e) {
164: log.error("Can't get beanInfo for "
165: + testBeanClass.getName(), e);
166: throw new Error(e.toString()); // Programming error. Don't
167: // continue.
168: }
169:
170: customizerClass = beanInfo.getBeanDescriptor()
171: .getCustomizerClass();
172:
173: // Creation of the customizer and GUI initialization is delayed until
174: // the
175: // first
176: // configure call. We don't need all that just to find out the static
177: // label, menu
178: // categories, etc!
179: initialized = false;
180: }
181:
182: private Customizer createCustomizer() {
183: try {
184: return (Customizer) customizerClass.newInstance();
185: } catch (InstantiationException e) {
186: log.error("Could not instantiate customizer of class "
187: + customizerClass, e);
188: throw new Error(e.toString());
189: } catch (IllegalAccessException e) {
190: log.error("Could not instantiate customizer of class "
191: + customizerClass, e);
192: throw new Error(e.toString());
193: }
194: }
195:
196: /*
197: * (non-Javadoc)
198: *
199: * @see org.apache.jmeter.gui.JMeterGUIComponent#getStaticLabel()
200: */
201: public String getStaticLabel() {
202: if (beanInfo == null)
203: return "null";// $NON-NLS-1$
204: return beanInfo.getBeanDescriptor().getDisplayName();
205: }
206:
207: /*
208: * (non-Javadoc)
209: *
210: * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement()
211: */
212: public TestElement createTestElement() {
213: try {
214: TestElement element = (TestElement) testBeanClass
215: .newInstance();
216: // configure(element);
217: // super.clear(); // set name, enabled.
218: modifyTestElement(element); // put the default values back into the
219: // new element
220: return element;
221: } catch (InstantiationException e) {
222: log.error("Can't create test element", e);
223: throw new Error(e.toString()); // Programming error. Don't
224: // continue.
225: } catch (IllegalAccessException e) {
226: log.error("Can't create test element", e);
227: throw new Error(e.toString()); // Programming error. Don't
228: // continue.
229: }
230: }
231:
232: /*
233: * (non-Javadoc)
234: *
235: * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(org.apache.jmeter.testelement.TestElement)
236: */
237: public void modifyTestElement(TestElement element) {
238: // Fetch data from screen fields
239: if (customizer instanceof GenericTestBeanCustomizer) {
240: GenericTestBeanCustomizer gtbc = (GenericTestBeanCustomizer) customizer;
241: gtbc.saveGuiFields();
242: }
243: configureTestElement(element);
244:
245: // Copy all property values from the map into the element:
246: PropertyDescriptor[] props = beanInfo.getPropertyDescriptors();
247: for (int i = 0; i < props.length; i++) {
248: String name = props[i].getName();
249: Object value = propertyMap.get(name);
250: log.debug("Modify " + name + " to " + value);
251: if (value == null) {
252: Object valueNotUnDefined = props[i]
253: .getValue(BeanInfoSupport.NOT_UNDEFINED);
254: if (valueNotUnDefined != null
255: && ((Boolean) valueNotUnDefined).booleanValue()) {
256: setPropertyInElement(element, name, props[i]
257: .getValue(BeanInfoSupport.DEFAULT));
258: } else {
259: element.removeProperty(name);
260: }
261: } else {
262: setPropertyInElement(element, name, propertyMap
263: .get(name));
264: }
265: }
266: }
267:
268: /**
269: * @param element
270: * @param name
271: */
272: private void setPropertyInElement(TestElement element, String name,
273: Object value) {
274: JMeterProperty jprop = AbstractProperty.createProperty(value);
275: jprop.setName(name);
276: element.setProperty(jprop);
277: }
278:
279: /*
280: * (non-Javadoc)
281: *
282: * @see org.apache.jmeter.gui.JMeterGUIComponent#createPopupMenu()
283: */
284: public JPopupMenu createPopupMenu() {
285: // TODO: this menu is too wide (allows, e.g. to add controllers, no
286: // matter what the type of the element).
287: // Change to match the actual bean's capabilities.
288: if (Timer.class.isAssignableFrom(testBeanClass))// HACK: Fix one such problem
289: {
290: return MenuFactory.getDefaultTimerMenu();
291: } else if (Sampler.class.isAssignableFrom(testBeanClass)) {
292: return MenuFactory.getDefaultSamplerMenu();
293: } else if (ConfigTestElement.class
294: .isAssignableFrom(testBeanClass)) {
295: return MenuFactory.getDefaultConfigElementMenu();
296: } else if (Assertion.class.isAssignableFrom(testBeanClass)) {
297: return MenuFactory.getDefaultAssertionMenu();
298: } else if (PostProcessor.class.isAssignableFrom(testBeanClass)
299: || PreProcessor.class.isAssignableFrom(testBeanClass)) {
300: return MenuFactory.getDefaultExtractorMenu();
301: } else if (AbstractListenerElement.class
302: .isAssignableFrom(testBeanClass)) {
303: return MenuFactory.getDefaultVisualizerMenu();
304: } else
305: return MenuFactory.getDefaultControllerMenu();
306: }
307:
308: /*
309: * (non-Javadoc)
310: *
311: * @see org.apache.jmeter.gui.JMeterGUIComponent#configure(org.apache.jmeter.testelement.TestElement)
312: */
313: public void configure(TestElement element) {
314: if (!initialized)
315: init();
316: clearGui();
317:
318: super .configure(element);
319:
320: // Copy all property values into the map:
321: for (PropertyIterator jprops = element.propertyIterator(); jprops
322: .hasNext();) {
323: JMeterProperty jprop = jprops.next();
324: propertyMap.put(jprop.getName(), jprop.getObjectValue());
325: }
326:
327: if (customizer != null) {
328: customizer.setObject(propertyMap);
329: } else {
330: if (initialized)
331: remove(customizerIndexInPanel);
332: Customizer c = (Customizer) customizers.get(element);
333: if (c == null) {
334: c = createCustomizer();
335: c.setObject(propertyMap);
336: customizers.put(element, c);
337: }
338: add((Component) c, BorderLayout.CENTER);
339: }
340:
341: initialized = true;
342: }
343:
344: /*
345: * (non-Javadoc)
346: *
347: * @see org.apache.jmeter.gui.JMeterGUIComponent#getMenuCategories()
348: */
349: public Collection getMenuCategories() {
350: List menuCategories = new LinkedList();
351: BeanDescriptor bd = beanInfo.getBeanDescriptor();
352:
353: // We don't want to show expert beans in the menus unless we're
354: // in expert mode:
355: if (bd.isExpert() && !JMeterUtils.isExpertMode()) {
356: return null;
357: }
358:
359: int matches = 0; // How many classes can we assign from?
360: // TODO: there must be a nicer way...
361: if (Assertion.class.isAssignableFrom(testBeanClass)) {
362: menuCategories.add(MenuFactory.ASSERTIONS);
363: bd.setValue(TestElement.GUI_CLASS,
364: AbstractAssertionGui.class.getName());
365: matches++;
366: }
367: if (ConfigElement.class.isAssignableFrom(testBeanClass)) {
368: menuCategories.add(MenuFactory.CONFIG_ELEMENTS);
369: bd.setValue(TestElement.GUI_CLASS, AbstractConfigGui.class
370: .getName());
371: matches++;
372: }
373: if (Controller.class.isAssignableFrom(testBeanClass)) {
374: menuCategories.add(MenuFactory.CONTROLLERS);
375: bd.setValue(TestElement.GUI_CLASS,
376: AbstractControllerGui.class.getName());
377: matches++;
378: }
379: if (Visualizer.class.isAssignableFrom(testBeanClass)) {
380: menuCategories.add(MenuFactory.LISTENERS);
381: bd.setValue(TestElement.GUI_CLASS, AbstractVisualizer.class
382: .getName());
383: matches++;
384: }
385: if (PostProcessor.class.isAssignableFrom(testBeanClass)) {
386: menuCategories.add(MenuFactory.POST_PROCESSORS);
387: bd.setValue(TestElement.GUI_CLASS,
388: AbstractPostProcessorGui.class.getName());
389: matches++;
390: }
391: if (PreProcessor.class.isAssignableFrom(testBeanClass)) {
392: matches++;
393: menuCategories.add(MenuFactory.PRE_PROCESSORS);
394: bd.setValue(TestElement.GUI_CLASS,
395: AbstractPreProcessorGui.class.getName());
396: }
397: if (Sampler.class.isAssignableFrom(testBeanClass)) {
398: matches++;
399: menuCategories.add(MenuFactory.SAMPLERS);
400: bd.setValue(TestElement.GUI_CLASS, AbstractSamplerGui.class
401: .getName());
402: }
403: if (Timer.class.isAssignableFrom(testBeanClass)) {
404: matches++;
405: menuCategories.add(MenuFactory.TIMERS);
406: bd.setValue(TestElement.GUI_CLASS, AbstractTimerGui.class
407: .getName());
408: }
409: if (matches == 0) {
410: log.error("Could not assign GUI class to "
411: + testBeanClass.getName());
412: } else if (matches > 1) {// may be impossible, but no harm in
413: // checking ...
414: log.error("More than 1 GUI class found for "
415: + testBeanClass.getName());
416: }
417: return menuCategories;
418: }
419:
420: private void init() {
421: setLayout(new BorderLayout(0, 5));
422:
423: setBorder(makeBorder());
424: add(makeTitlePanel(), BorderLayout.NORTH);
425:
426: customizerIndexInPanel = getComponentCount();
427:
428: if (customizerClass == null) {
429: customizer = new GenericTestBeanCustomizer(beanInfo);
430: } else if (SharedCustomizer.class
431: .isAssignableFrom(customizerClass)) {
432: customizer = createCustomizer();
433: }
434:
435: if (customizer != null)
436: add((Component) customizer, BorderLayout.CENTER);
437: }
438:
439: /*
440: * (non-Javadoc)
441: *
442: * @see org.apache.jmeter.gui.JMeterGUIComponent#getLabelResource()
443: */
444: public String getLabelResource() {
445: // @see getStaticLabel
446: return null;
447: }
448:
449: /*
450: * (non-Javadoc)
451: *
452: * @see org.apache.jmeter.gui.JMeterGUIComponent#clearGui()
453: */
454: public void clearGui() {
455: super .clearGui();
456: if (customizer instanceof GenericTestBeanCustomizer) {
457: GenericTestBeanCustomizer gtbc = (GenericTestBeanCustomizer) customizer;
458: gtbc.clearGuiFields();
459: }
460: }
461: }
|