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,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.jmeter.gui;
020:
021: import java.awt.Component;
022: import java.awt.event.MouseEvent;
023: import java.beans.Introspector;
024: import java.io.IOException;
025: import java.util.HashMap;
026: import java.util.Map;
027:
028: import javax.swing.JPopupMenu;
029:
030: import org.apache.jmeter.exceptions.IllegalUserActionException;
031: import org.apache.jmeter.report.engine.ValueReplacer;
032: import org.apache.jmeter.report.gui.tree.ReportTreeListener;
033: import org.apache.jmeter.report.gui.tree.ReportTreeModel;
034: import org.apache.jmeter.report.gui.tree.ReportTreeNode;
035: import org.apache.jmeter.services.FileServer;
036: import org.apache.jmeter.testbeans.TestBean;
037: import org.apache.jmeter.testbeans.gui.TestBeanGUI;
038: import org.apache.jmeter.testelement.ReportPlan;
039: import org.apache.jmeter.testelement.TestElement;
040: import org.apache.jmeter.util.JMeterUtils;
041: import org.apache.jmeter.util.LocaleChangeEvent;
042: import org.apache.jmeter.util.LocaleChangeListener;
043: import org.apache.jorphan.collections.HashTree;
044: import org.apache.jorphan.logging.LoggingManager;
045: import org.apache.log.Logger;
046:
047: /**
048: * ReportGuiPackage is based on GuiPackage, but with changes for
049: * the reporting tool. Because of how the gui components work, it
050: * was safer to just make a new class, rather than braking existing
051: * JMeter gui code.
052: *
053: */
054: public final class ReportGuiPackage implements LocaleChangeListener {
055: /** Logging. */
056: private static transient Logger log = LoggingManager
057: .getLoggerForClass();
058:
059: /** Singleton instance. */
060: private static ReportGuiPackage guiPack;
061:
062: /**
063: * Flag indicating whether or not parts of the tree have changed since they
064: * were last saved.
065: */
066: private boolean dirty = false;
067:
068: /**
069: * Map from TestElement to JMeterGUIComponent, mapping the nodes in the tree
070: * to their corresponding GUI components.
071: */
072: private Map nodesToGui = new HashMap();
073:
074: /**
075: * Map from Class to JMeterGUIComponent, mapping the Class of a GUI
076: * component to an instance of that component.
077: */
078: private Map guis = new HashMap();
079:
080: /**
081: * Map from Class to TestBeanGUI, mapping the Class of a TestBean to an
082: * instance of TestBeanGUI to be used to edit such components.
083: */
084: private Map testBeanGUIs = new HashMap();
085:
086: /** The currently selected node in the tree. */
087: private ReportTreeNode currentNode = null;
088:
089: private boolean currentNodeUpdated = false;
090:
091: /** The model for JMeter's test tree. */
092: private ReportTreeModel treeModel;
093:
094: /** The listener for JMeter's test tree. */
095: private ReportTreeListener treeListener;
096:
097: /** The main JMeter frame. */
098: private ReportMainFrame mainFrame;
099:
100: /**
101: * Private constructor to permit instantiation only from within this class.
102: * Use {@link #getInstance()} to retrieve a singleton instance.
103: */
104: private ReportGuiPackage() {
105: JMeterUtils.addLocaleChangeListener(this );
106: }
107:
108: /**
109: * Retrieve the singleton GuiPackage instance.
110: *
111: * @return the GuiPackage instance
112: */
113: public static ReportGuiPackage getInstance() {
114: if (guiPack == null) {
115: log.error("ReportGuiPackage is null");
116: }
117: return guiPack;
118: }
119:
120: /**
121: * When GuiPackage is requested for the first time, it should be given
122: * handles to JMeter's Tree Listener and TreeModel.
123: *
124: * @param listener
125: * the TreeListener for JMeter's test tree
126: * @param treeModel
127: * the model for JMeter's test tree
128: *
129: * @return GuiPackage
130: */
131: public static ReportGuiPackage getInstance(
132: ReportTreeListener listener, ReportTreeModel treeModel) {
133: if (guiPack == null) {
134: guiPack = new ReportGuiPackage();
135: guiPack.setTreeListener(listener);
136: guiPack.setTreeModel(treeModel);
137: }
138: return guiPack;
139: }
140:
141: /**
142: * Get a JMeterGUIComponent for the specified test element. If the GUI has
143: * already been created, that instance will be returned. Otherwise, if a GUI
144: * component of the same type has been created, and the component is not
145: * marked as an {@link UnsharedComponent}, that shared component will be
146: * returned. Otherwise, a new instance of the component will be created. The
147: * TestElement's GUI_CLASS property will be used to determine the
148: * appropriate type of GUI component to use.
149: *
150: * @param node
151: * the test element which this GUI is being created for
152: *
153: * @return the GUI component corresponding to the specified test element
154: */
155: public JMeterGUIComponent getGui(TestElement node) {
156: String testClassName = node
157: .getPropertyAsString(TestElement.TEST_CLASS);
158: String guiClassName = node
159: .getPropertyAsString(TestElement.GUI_CLASS);
160: try {
161: Class testClass;
162: if (testClassName.equals("")) {
163: testClass = node.getClass();
164: } else {
165: testClass = Class.forName(testClassName);
166: }
167: Class guiClass = null;
168: if (!guiClassName.equals("")) {
169: guiClass = Class.forName(guiClassName);
170: }
171: return getGui(node, guiClass, testClass);
172: } catch (ClassNotFoundException e) {
173: log.error("Could not get GUI for " + node, e);
174: return null;
175: }
176: }
177:
178: /**
179: * Get a JMeterGUIComponent for the specified test element. If the GUI has
180: * already been created, that instance will be returned. Otherwise, if a GUI
181: * component of the same type has been created, and the component is not
182: * marked as an {@link UnsharedComponent}, that shared component will be
183: * returned. Otherwise, a new instance of the component will be created.
184: *
185: * @param node
186: * the test element which this GUI is being created for
187: * @param guiClass
188: * the fully qualifed class name of the GUI component which will
189: * be created if it doesn't already exist
190: * @param testClass
191: * the fully qualifed class name of the test elements which have
192: * to be edited by the returned GUI component
193: *
194: * @return the GUI component corresponding to the specified test element
195: */
196: public JMeterGUIComponent getGui(TestElement node, Class guiClass,
197: Class testClass) {
198: try {
199: JMeterGUIComponent comp = (JMeterGUIComponent) nodesToGui
200: .get(node);
201: if (comp == null) {
202: comp = getGuiFromCache(guiClass, testClass);
203: nodesToGui.put(node, comp);
204: }
205: log.debug("Gui retrieved = " + comp);
206: return comp;
207: } catch (Exception e) {
208: log.error("Problem retrieving gui", e);
209: return null;
210: }
211: }
212:
213: /**
214: * Remove a test element from the tree. This removes the reference to any
215: * associated GUI component.
216: *
217: * @param node
218: * the test element being removed
219: */
220: public void removeNode(TestElement node) {
221: nodesToGui.remove(node);
222: }
223:
224: /**
225: * Convenience method for grabbing the gui for the current node.
226: *
227: * @return the GUI component associated with the currently selected node
228: */
229: public JMeterGUIComponent getCurrentGui() {
230: try {
231: updateCurrentNode();
232: TestElement curNode = treeListener.getCurrentNode()
233: .getTestElement();
234: JMeterGUIComponent comp = getGui(curNode);
235: comp.clearGui();
236: log.debug("Updating gui to new node");
237: comp.configure(curNode);
238: currentNodeUpdated = false;
239: return comp;
240: } catch (Exception e) {
241: log.error("Problem retrieving gui", e);
242: return null;
243: }
244: }
245:
246: /**
247: * Find the JMeterTreeNode for a certain TestElement object.
248: *
249: * @param userObject
250: * the test element to search for
251: * @return the tree node associated with the test element
252: */
253: public ReportTreeNode getNodeOf(TestElement userObject) {
254: return treeModel.getNodeOf(userObject);
255: }
256:
257: /**
258: * Create a TestElement corresponding to the specified GUI class.
259: *
260: * @param guiClass
261: * the fully qualified class name of the GUI component or a
262: * TestBean class for TestBeanGUIs.
263: * @param testClass
264: * the fully qualified class name of the test elements edited by
265: * this GUI component.
266: * @return the test element corresponding to the specified GUI class.
267: */
268: public TestElement createTestElement(Class guiClass, Class testClass) {
269: try {
270: JMeterGUIComponent comp = getGuiFromCache(guiClass,
271: testClass);
272: comp.clearGui();
273: TestElement node = comp.createTestElement();
274: nodesToGui.put(node, comp);
275: return node;
276: } catch (Exception e) {
277: log.error("Problem retrieving gui", e);
278: return null;
279: }
280: }
281:
282: /**
283: * Create a TestElement for a GUI or TestBean class.
284: * <p>
285: * This is a utility method to help actions do with one single String
286: * parameter.
287: *
288: * @param objClass
289: * the fully qualified class name of the GUI component or of the
290: * TestBean subclass for which a TestBeanGUI is wanted.
291: * @return the test element corresponding to the specified GUI class.
292: */
293: public TestElement createTestElement(String objClass) {
294: JMeterGUIComponent comp;
295: Class c;
296: try {
297: c = Class.forName(objClass);
298: if (TestBean.class.isAssignableFrom(c)) {
299: comp = getGuiFromCache(TestBeanGUI.class, c);
300: } else {
301: comp = getGuiFromCache(c, null);
302: }
303: comp.clearGui();
304: TestElement node = comp.createTestElement();
305: nodesToGui.put(node, comp);
306: return node;
307: } catch (NoClassDefFoundError e) {
308: log.error("Problem retrieving gui for " + objClass, e);
309: throw new RuntimeException(e.toString()); // Probably a missing
310: // jar
311: } catch (ClassNotFoundException e) {
312: log.error("Problem retrieving gui for " + objClass, e);
313: throw new RuntimeException(e.toString()); // Programming error:
314: // bail out.
315: } catch (InstantiationException e) {
316: log.error("Problem retrieving gui for " + objClass, e);
317: throw new RuntimeException(e.toString()); // Programming error:
318: // bail out.
319: } catch (IllegalAccessException e) {
320: log.error("Problem retrieving gui for " + objClass, e);
321: throw new RuntimeException(e.toString()); // Programming error:
322: // bail out.
323: }
324: }
325:
326: /**
327: * Get an instance of the specified JMeterGUIComponent class. If an instance
328: * of the GUI class has previously been created and it is not marked as an
329: * {@link UnsharedComponent}, that shared instance will be returned.
330: * Otherwise, a new instance of the component will be created, and shared
331: * components will be cached for future retrieval.
332: *
333: * @param guiClass
334: * the fully qualified class name of the GUI component. This
335: * class must implement JMeterGUIComponent.
336: * @param testClass
337: * the fully qualified class name of the test elements edited by
338: * this GUI component. This class must implement TestElement.
339: * @return an instance of the specified class
340: *
341: * @throws InstantiationException
342: * if an instance of the object cannot be created
343: * @throws IllegalAccessException
344: * if access rights do not allow the default constructor to be
345: * called
346: * @throws ClassNotFoundException
347: * if the specified GUI class cannot be found
348: */
349: private JMeterGUIComponent getGuiFromCache(Class guiClass,
350: Class testClass) throws InstantiationException,
351: IllegalAccessException {
352: JMeterGUIComponent comp;
353: if (guiClass == TestBeanGUI.class) {
354: comp = (TestBeanGUI) testBeanGUIs.get(testClass);
355: if (comp == null) {
356: comp = new TestBeanGUI(testClass);
357: testBeanGUIs.put(testClass, comp);
358: }
359: } else {
360: comp = (JMeterGUIComponent) guis.get(guiClass);
361: if (comp == null) {
362: comp = (JMeterGUIComponent) guiClass.newInstance();
363: if (!(comp instanceof UnsharedComponent)) {
364: guis.put(guiClass, comp);
365: }
366: }
367: }
368: return comp;
369: }
370:
371: /**
372: * Update the GUI for the currently selected node. The GUI component is
373: * configured to reflect the settings in the current tree node.
374: *
375: */
376: public void updateCurrentGui() {
377: updateCurrentNode();
378: currentNode = treeListener.getCurrentNode();
379: TestElement element = currentNode.getTestElement();
380: JMeterGUIComponent comp = getGui(element);
381: comp.configure(element);
382: currentNodeUpdated = false;
383: }
384:
385: /**
386: * This method should be called in order for GuiPackage to change the
387: * current node. This will save any changes made to the earlier node before
388: * choosing the new node.
389: */
390: public void updateCurrentNode() {
391: try {
392: if (currentNode != null && !currentNodeUpdated) {
393: log.debug("Updating current node "
394: + currentNode.getName());
395: JMeterGUIComponent comp = getGui(currentNode
396: .getTestElement());
397: TestElement el = currentNode.getTestElement();
398: comp.modifyTestElement(el);
399: }
400: if (currentNode != treeListener.getCurrentNode()) {
401: currentNodeUpdated = true;
402: }
403: currentNode = treeListener.getCurrentNode();
404: } catch (Exception e) {
405: log.error("Problem retrieving gui", e);
406: }
407: }
408:
409: public ReportTreeNode getCurrentNode() {
410: return treeListener.getCurrentNode();
411: }
412:
413: public TestElement getCurrentElement() {
414: return getCurrentNode().getTestElement();
415: }
416:
417: /**
418: * The dirty property is a flag that indicates whether there are parts of
419: * JMeter's test tree that the user has not saved since last modification.
420: * Various (@link Command actions) set this property when components are
421: * modified/created/saved.
422: *
423: * @param dirty
424: * the new value of the dirty flag
425: */
426: public void setDirty(boolean dirty) {
427: this .dirty = dirty;
428: }
429:
430: /**
431: * Retrieves the state of the 'dirty' property, a flag that indicates if
432: * there are test tree components that have been modified since they were
433: * last saved.
434: *
435: * @return true if some tree components have been modified since they were
436: * last saved, false otherwise
437: */
438: public boolean isDirty() {
439: return dirty;
440: }
441:
442: /**
443: * Add a subtree to the currently selected node.
444: *
445: * @param subTree
446: * the subtree to add.
447: *
448: * @return the resulting subtree starting with the currently selected node
449: *
450: * @throws IllegalUserActionException
451: * if a subtree cannot be added to the currently selected node
452: */
453: public HashTree addSubTree(HashTree subTree)
454: throws IllegalUserActionException {
455: return treeModel.addSubTree(subTree, treeListener
456: .getCurrentNode());
457: }
458:
459: /**
460: * Get the currently selected subtree.
461: *
462: * @return the subtree of the currently selected node
463: */
464: public HashTree getCurrentSubTree() {
465: return treeModel.getCurrentSubTree(treeListener
466: .getCurrentNode());
467: }
468:
469: /**
470: * Get the model for JMeter's test tree.
471: *
472: * @return the JMeter tree model
473: */
474: public ReportTreeModel getTreeModel() {
475: return treeModel;
476: }
477:
478: /**
479: * Set the model for JMeter's test tree.
480: *
481: * @param newTreeModel
482: * the new JMeter tree model
483: */
484: public void setTreeModel(ReportTreeModel newTreeModel) {
485: treeModel = newTreeModel;
486: }
487:
488: /**
489: * Get a ValueReplacer for the test tree.
490: *
491: * @return a ValueReplacer configured for the test tree
492: */
493: public ValueReplacer getReplacer() {
494: return new ValueReplacer(
495: (ReportPlan) ((ReportTreeNode) getTreeModel()
496: .getReportPlan().getArray()[0])
497: .getTestElement());
498: }
499:
500: /**
501: * Set the main JMeter frame.
502: *
503: * @param newMainFrame
504: * the new JMeter main frame
505: */
506: public void setMainFrame(ReportMainFrame newMainFrame) {
507: this .mainFrame = newMainFrame;
508: }
509:
510: /**
511: * Get the main JMeter frame.
512: *
513: * @return the main JMeter frame
514: */
515: public ReportMainFrame getMainFrame() {
516: return this .mainFrame;
517: }
518:
519: /**
520: * Set the listener for JMeter's test tree.
521: *
522: * @param newTreeListener
523: * the new JMeter test tree listener
524: */
525: public void setTreeListener(ReportTreeListener newTreeListener) {
526: treeListener = newTreeListener;
527: }
528:
529: /**
530: * Get the listener for JMeter's test tree.
531: *
532: * @return the JMeter test tree listener
533: */
534: public ReportTreeListener getTreeListener() {
535: return treeListener;
536: }
537:
538: /**
539: * Display the specified popup menu with the source component and location
540: * from the specified mouse event.
541: *
542: * @param e
543: * the mouse event causing this popup to be displayed
544: * @param popup
545: * the popup menu to display
546: */
547: public void displayPopUp(MouseEvent e, JPopupMenu popup) {
548: displayPopUp((Component) e.getSource(), e, popup);
549: }
550:
551: /**
552: * Display the specified popup menu at the location specified by a mouse
553: * event with the specified source component.
554: *
555: * @param invoker
556: * the source component
557: * @param e
558: * the mouse event causing this popup to be displayed
559: * @param popup
560: * the popup menu to display
561: */
562: public void displayPopUp(Component invoker, MouseEvent e,
563: JPopupMenu popup) {
564: if (popup != null) {
565: log.debug("Showing pop up for " + invoker + " at x,y = "
566: + e.getX() + "," + e.getY());
567:
568: popup.pack();
569: popup.show(invoker, e.getX(), e.getY());
570: popup.setVisible(true);
571: popup.requestFocus();
572: }
573: }
574:
575: /*
576: * (non-Javadoc)
577: *
578: * @see org.apache.jmeter.util.LocaleChangeListener#localeChanged(org.apache.jmeter.util.LocaleChangeEvent)
579: */
580: public void localeChanged(LocaleChangeEvent event) {
581: // FIrst make sure we save the content of the current GUI (since we
582: // will flush it away):
583: updateCurrentNode();
584:
585: // Forget about all GUIs we've created so far: we'll need to re-created
586: // them all!
587: guis = new HashMap();
588: nodesToGui = new HashMap();
589: testBeanGUIs = new HashMap();
590:
591: // BeanInfo objects also contain locale-sensitive data -- flush them
592: // away:
593: Introspector.flushCaches();
594:
595: // Now put the current GUI in place. [This code was copied from the
596: // EditCommand action -- we can't just trigger the action because that
597: // would populate the current node with the contents of the new GUI --
598: // which is empty.]
599: ReportMainFrame mf = getMainFrame(); // Fetch once
600: if (mf == null) // Probably caused by unit testing on headless system
601: {
602: log.warn("Mainframe is null");
603: } else {
604: mf.setMainPanel((javax.swing.JComponent) getCurrentGui());
605: mf.setEditMenu(getTreeListener().getCurrentNode()
606: .createPopupMenu());
607: }
608: }
609:
610: private String reportPlanFile;
611:
612: /**
613: * Sets the filepath of the current test plan. It's shown in the main frame
614: * title and used on saving.
615: *
616: * @param f
617: */
618: public void setReportPlanFile(String f) {
619: reportPlanFile = f;
620: ReportGuiPackage.getInstance().getMainFrame()
621: .setExtendedFrameTitle(reportPlanFile);
622: try {
623: FileServer.getFileServer().setBasedir(reportPlanFile);
624: } catch (IOException e1) {
625: log.error("Failure setting file server's base dir", e1);
626: }
627: }
628:
629: public String getReportPlanFile() {
630: return reportPlanFile;
631: }
632: }
|