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.visualizers.gui;
020:
021: import java.awt.Component;
022: import java.awt.Container;
023: import java.awt.event.ActionEvent;
024: import java.awt.event.ActionListener;
025: import java.util.Arrays;
026: import java.util.Collection;
027:
028: import javax.swing.JButton;
029: import javax.swing.JCheckBox;
030: import javax.swing.JLabel;
031: import javax.swing.JPopupMenu;
032: import javax.swing.event.ChangeEvent;
033: import javax.swing.event.ChangeListener;
034:
035: import org.apache.jmeter.gui.AbstractJMeterGuiComponent;
036: import org.apache.jmeter.gui.GuiPackage;
037: import org.apache.jmeter.gui.SavePropertyDialog;
038: import org.apache.jmeter.gui.UnsharedComponent;
039: import org.apache.jmeter.gui.util.FilePanel;
040: import org.apache.jmeter.gui.util.MenuFactory;
041: import org.apache.jmeter.reporters.AbstractListenerElement;
042: import org.apache.jmeter.reporters.ResultCollector;
043: import org.apache.jmeter.samplers.Clearable;
044: import org.apache.jmeter.samplers.SampleSaveConfiguration;
045: import org.apache.jmeter.testelement.TestElement;
046: import org.apache.jmeter.util.JMeterUtils;
047: import org.apache.jmeter.visualizers.Visualizer;
048: import org.apache.jorphan.gui.ComponentUtil;
049: import org.apache.jorphan.logging.LoggingManager;
050: import org.apache.log.Logger;
051:
052: /**
053: * This is the base class for JMeter GUI components which can display test
054: * results in some way. It provides the following conveniences to developers:
055: * <ul>
056: * <li>Implements the
057: * {@link org.apache.jmeter.gui.JMeterGUIComponent JMeterGUIComponent} interface
058: * that allows your Gui visualizer to "plug-in" to the JMeter GUI environment.
059: * Provides implementations for the following methods:
060: * <ul>
061: * <li>{@link org.apache.jmeter.gui.JMeterGUIComponent#configure(TestElement) configure(TestElement)}.
062: * Any additional parameters of your Visualizer need to be handled by you.</li>
063: * <li>{@link org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() createTestElement()}.
064: * For most purposes, the default
065: * {@link org.apache.jmeter.reporters.ResultCollector ResultCollector} created
066: * by this method is sufficient.</li>
067: * <li>{@link org.apache.jmeter.gui.JMeterGUIComponent#getMenuCategories getMenuCategories()}.
068: * To control where in the GUI your visualizer can be added.</li>
069: * <li>{@link org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) modifyTestElement(TestElement)}.
070: * Again, additional parameters you require have to be handled by you.</li>
071: * <li>{@link org.apache.jmeter.gui.JMeterGUIComponent#createPopupMenu() createPopupMenu()}.</li>
072: * </ul>
073: * </li>
074: * <li>Provides convenience methods to help you make a JMeter-compatible GUI:
075: * <ul>
076: * <li>{@link #makeTitlePanel()}. Returns a panel that includes the name of
077: * the component, and a FilePanel that allows users to control what file samples
078: * are logged to.</li>
079: * <li>{@link #getModel()} and {@link #setModel(ResultCollector)} methods for
080: * setting and getting the model class that handles the receiving and logging of
081: * sample results.</li>
082: * </ul>
083: * </li>
084: * </ul>
085: * For most developers, making a new visualizer is primarly for the purpose of
086: * either calculating new statistics on the sample results that other
087: * visualizers don't calculate, or displaying the results visually in a new and
088: * interesting way. Making a new visualizer for either of these purposes is easy -
089: * just extend this class and implement the
090: * {@link org.apache.jmeter.visualizers.Visualizer#add add(SampleResult)}
091: * method and display the results as you see fit. This AbstractVisualizer and
092: * the default
093: * {@link org.apache.jmeter.reporters.ResultCollector ResultCollector} handle
094: * logging and registering to receive SampleEvents for you - all you need to do
095: * is include the JPanel created by makeTitlePanel somewhere in your gui to
096: * allow users set the log file.
097: * <p>
098: * If you are doing more than that, you may need to extend
099: * {@link org.apache.jmeter.reporters.ResultCollector ResultCollector} as well
100: * and modify the {@link #configure(TestElement)},
101: * {@link #modifyTestElement(TestElement)}, and {@link #createTestElement()}
102: * methods to create and modify your alternate ResultCollector. For an example
103: * of this, see the
104: * {@link org.apache.jmeter.visualizers.MailerVisualizer MailerVisualizer}.
105: * <p>
106: *
107: */
108: public abstract class AbstractVisualizer extends
109: AbstractJMeterGuiComponent implements Visualizer,
110: ChangeListener, UnsharedComponent, Clearable {
111: /** Logging. */
112: private static final Logger log = LoggingManager
113: .getLoggerForClass();
114:
115: /** A panel allowing results to be saved. */
116: private FilePanel filePanel;
117:
118: /** A checkbox choosing whether or not only errors should be logged. */
119: private JCheckBox errorLogging;
120:
121: /* A checkbox choosing whether or not only successes should be logged. */
122: private JCheckBox successOnlyLogging;
123:
124: private JButton saveConfigButton;
125:
126: protected ResultCollector collector = new ResultCollector();
127:
128: protected boolean isStats = false;
129:
130: public AbstractVisualizer() {
131: super ();
132:
133: // errorLogging and successOnlyLogging are mutually exclusive
134: errorLogging = new JCheckBox(JMeterUtils
135: .getResString("log_errors_only")); // $NON-NLS-1$
136: errorLogging.addActionListener(new ActionListener() {
137: public void actionPerformed(ActionEvent e) {
138: if (errorLogging.isSelected())
139: successOnlyLogging.setSelected(false);
140: }
141: });
142: successOnlyLogging = new JCheckBox(JMeterUtils
143: .getResString("log_success_only")); // $NON-NLS-1$
144: successOnlyLogging.addActionListener(new ActionListener() {
145: public void actionPerformed(ActionEvent e) {
146: if (successOnlyLogging.isSelected())
147: errorLogging.setSelected(false);
148: }
149: });
150: saveConfigButton = new JButton(JMeterUtils
151: .getResString("config_save_settings")); // $NON-NLS-1$
152: saveConfigButton.addActionListener(new ActionListener() {
153: public void actionPerformed(ActionEvent e) {
154: SavePropertyDialog d = new SavePropertyDialog(
155: GuiPackage.getInstance().getMainFrame(),
156: JMeterUtils
157: .getResString("sample_result_save_configuration"), // $NON-NLS-1$
158: true, collector.getSaveConfig());
159: d.pack();
160: ComponentUtil.centerComponentInComponent(GuiPackage
161: .getInstance().getMainFrame(), d);
162: d.setVisible(true);
163: }
164: });
165:
166: filePanel = new FilePanel(JMeterUtils
167: .getResString("file_visualizer_output_file"), ".jtl"); // $NON-NLS-1$ $NON-NLS-2$
168: filePanel.addChangeListener(this );
169: filePanel.add(new JLabel(JMeterUtils.getResString("log_only"))); // $NON-NLS-1$
170: filePanel.add(errorLogging);
171: filePanel.add(successOnlyLogging);
172: filePanel.add(saveConfigButton);
173:
174: }
175:
176: public boolean isStats() {
177: return isStats;
178: }
179:
180: /**
181: * Gets the checkbox which selects whether or not only errors should be
182: * logged. Subclasses don't normally need to worry about this checkbox,
183: * because it is automatically added to the GUI in {@link #makeTitlePanel()},
184: * and the behavior is handled in this base class.
185: *
186: * @return the error logging checkbox
187: */
188: protected JCheckBox getErrorLoggingCheckbox() {
189: return errorLogging;
190: }
191:
192: /**
193: * Provides access to the ResultCollector model class for extending
194: * implementations. Using this method and setModel(ResultCollector) is only
195: * necessary if your visualizer requires a differently behaving
196: * ResultCollector. Using these methods will allow maximum reuse of the
197: * methods provided by AbstractVisualizer in this event.
198: */
199: protected ResultCollector getModel() {
200: return collector;
201: }
202:
203: /**
204: * Gets the file panel which allows the user to save results to a file.
205: * Subclasses don't normally need to worry about this panel, because it is
206: * automatically added to the GUI in {@link #makeTitlePanel()}, and the
207: * behavior is handled in this base class.
208: *
209: * @return the file panel allowing users to save results
210: */
211: protected Component getFilePanel() {
212: return filePanel;
213: }
214:
215: /**
216: * Sets the filename which results will be saved to. This will set the
217: * filename in the FilePanel. Subclasses don't normally need to call this
218: * method, because configuration of the FilePanel is handled in this base
219: * class.
220: *
221: * @param filename
222: * the new filename
223: *
224: * @see #getFilePanel()
225: */
226: public void setFile(String filename) {
227: // TODO: Does this method need to be public? It isn't currently
228: // called outside of this class.
229: filePanel.setFilename(filename);
230: }
231:
232: /**
233: * Gets the filename which has been entered in the FilePanel. Subclasses
234: * don't normally need to call this method, because configuration of the
235: * FilePanel is handled in this base class.
236: *
237: * @return the current filename
238: *
239: * @see #getFilePanel()
240: */
241: public String getFile() {
242: // TODO: Does this method need to be public? It isn't currently
243: // called outside of this class.
244: return filePanel.getFilename();
245: }
246:
247: /**
248: * When a user right-clicks on the component in the test tree, or selects
249: * the edit menu when the component is selected, the component will be asked
250: * to return a JPopupMenu that provides all the options available to the
251: * user from this component.
252: * <p>
253: * This implementation returns menu items appropriate for most visualizer
254: * components.
255: *
256: * @return a JPopupMenu appropriate for the component.
257: */
258: public JPopupMenu createPopupMenu() {
259: return MenuFactory.getDefaultVisualizerMenu();
260: }
261:
262: /**
263: * Invoked when the target of the listener has changed its state. This
264: * implementation assumes that the target is the FilePanel, and will update
265: * the result collector for the new filename.
266: *
267: * @param e
268: * the event that has occurred
269: */
270: public void stateChanged(ChangeEvent e) {
271: log.debug("getting new collector");
272: collector = (ResultCollector) createTestElement();
273: collector.loadExistingFile();
274: }
275:
276: /**
277: * This is the list of menu categories this gui component will be available
278: * under. This implementation returns
279: * {@link org.apache.jmeter.gui.util.MenuFactory#LISTENERS}, which is
280: * appropriate for most visualizer components.
281: *
282: * @return a Collection of Strings, where each element is one of the
283: * constants defined in MenuFactory
284: */
285: public Collection getMenuCategories() {
286: return Arrays.asList(new String[] { MenuFactory.LISTENERS });
287: }
288:
289: /* Implements JMeterGUIComponent.createTestElement() */
290: public TestElement createTestElement() {
291: if (collector == null) {
292: collector = new ResultCollector();
293: }
294: modifyTestElement(collector);
295: return (TestElement) collector.clone();
296: }
297:
298: /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */
299: public void modifyTestElement(TestElement c) {
300: configureTestElement((AbstractListenerElement) c);
301: if (c instanceof ResultCollector) {
302: ResultCollector rc = (ResultCollector) c;
303: rc.setErrorLogging(errorLogging.isSelected());
304: rc.setSuccessOnlyLogging(successOnlyLogging.isSelected());
305: rc.setFilename(getFile());
306: collector = rc;
307: }
308: }
309:
310: /* Overrides AbstractJMeterGuiComponent.configure(TestElement) */
311: public void configure(TestElement el) {
312: super .configure(el);
313: setFile(el.getPropertyAsString(ResultCollector.FILENAME));
314: ResultCollector rc = (ResultCollector) el;
315: errorLogging.setSelected(rc.isErrorLogging());
316: successOnlyLogging.setSelected(rc.isSuccessOnlyLogging());
317: if (collector == null) {
318: collector = new ResultCollector();
319: }
320: collector.setSaveConfig((SampleSaveConfiguration) rc
321: .getSaveConfig().clone());
322: }
323:
324: /**
325: * This provides a convenience for extenders when they implement the
326: * {@link org.apache.jmeter.gui.JMeterGUIComponent#createTestElement()}
327: * method. This method will set the name, gui class, and test class for the
328: * created Test Element. It should be called by every extending class when
329: * creating Test Elements, as that will best assure consistent behavior.
330: *
331: * @param mc
332: * the TestElement being created.
333: */
334: protected void configureTestElement(AbstractListenerElement mc) {
335: // TODO: Should the method signature of this method be changed to
336: // match the super-implementation (using a TestElement parameter
337: // instead of AbstractListenerElement)? This would require an
338: // instanceof check before adding the listener (below), but would
339: // also make the behavior a bit more obvious for sub-classes -- the
340: // Java rules dealing with this situation aren't always intuitive,
341: // and a subclass may think it is calling this version of the method
342: // when it is really calling the superclass version instead.
343: super .configureTestElement(mc);
344: mc.setListener(this );
345: }
346:
347: /**
348: * Create a standard title section for JMeter components. This includes the
349: * title for the component and the Name Panel allowing the user to change
350: * the name for the component. The AbstractVisualizer also adds the
351: * FilePanel allowing the user to save the results, and the error logging
352: * checkbox, allowing the user to choose whether or not only errors should
353: * be logged.
354: * <p>
355: * This method is typically added to the top of the component at the
356: * beginning of the component's init method.
357: *
358: * @return a panel containing the component title, name panel, file panel,
359: * and error logging checkbox
360: */
361: protected Container makeTitlePanel() {
362: Container panel = super .makeTitlePanel();
363: // Note: the file panel already includes the error logging checkbox,
364: // so we don't have to add it explicitly.
365: panel.add(getFilePanel());
366: return panel;
367: }
368:
369: /**
370: * Provides extending classes the opportunity to set the ResultCollector
371: * model for the Visualizer. This is useful to allow maximum reuse of the
372: * methods from AbstractVisualizer.
373: *
374: * @param collector
375: */
376: protected void setModel(ResultCollector collector) {
377: this .collector = collector;
378: }
379:
380: public void clearGui() {
381: super.clearGui();
382: filePanel.clearGui();
383: }
384: }
|