1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package org.apache.jmeter.visualizers.gui; 20 21 import java.awt.Component; 22 import java.awt.Container; 23 import java.awt.event.ActionEvent; 24 import java.awt.event.ActionListener; 25 26 import javax.swing.JButton; 27 import javax.swing.JCheckBox; 28 import javax.swing.JLabel; 29 import javax.swing.event.ChangeEvent; 30 import javax.swing.event.ChangeListener; 31 32 import org.apache.jmeter.gui.GuiPackage; 33 import org.apache.jmeter.gui.SavePropertyDialog; 34 import org.apache.jmeter.gui.UnsharedComponent; 35 import org.apache.jmeter.gui.util.FilePanel; 36 import org.apache.jmeter.reporters.AbstractListenerElement; 37 import org.apache.jmeter.reporters.ResultCollector; 38 import org.apache.jmeter.samplers.Clearable; 39 import org.apache.jmeter.samplers.SampleSaveConfiguration; 40 import org.apache.jmeter.testelement.TestElement; 41 import org.apache.jmeter.util.JMeterUtils; 42 import org.apache.jmeter.visualizers.Visualizer; 43 import org.apache.jorphan.gui.ComponentUtil; 44 import org.apache.jorphan.logging.LoggingManager; 45 import org.apache.log.Logger; 46 47 /** 48 * This is the base class for JMeter GUI components which can display test 49 * results in some way. It provides the following conveniences to developers: 50 * <ul> 51 * <li>Implements the 52 * {@link org.apache.jmeter.gui.JMeterGUIComponent JMeterGUIComponent} interface 53 * that allows your Gui visualizer to "plug-in" to the JMeter GUI environment. 54 * Provides implementations for the following methods: 55 * <ul> 56 * <li>{@link org.apache.jmeter.gui.JMeterGUIComponent#configure(TestElement) configure(TestElement)}. 57 * Any additional parameters of your Visualizer need to be handled by you.</li> 58 * <li>{@link org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() createTestElement()}. 59 * For most purposes, the default 60 * {@link org.apache.jmeter.reporters.ResultCollector ResultCollector} created 61 * by this method is sufficient.</li> 62 * <li>{@link org.apache.jmeter.gui.JMeterGUIComponent#getMenuCategories getMenuCategories()}. 63 * To control where in the GUI your visualizer can be added.</li> 64 * <li>{@link org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) modifyTestElement(TestElement)}. 65 * Again, additional parameters you require have to be handled by you.</li> 66 * <li>{@link org.apache.jmeter.gui.JMeterGUIComponent#createPopupMenu() createPopupMenu()}.</li> 67 * </ul> 68 * </li> 69 * <li>Provides convenience methods to help you make a JMeter-compatible GUI: 70 * <ul> 71 * <li>{@link #makeTitlePanel()}. Returns a panel that includes the name of 72 * the component, and a FilePanel that allows users to control what file samples 73 * are logged to.</li> 74 * <li>{@link #getModel()} and {@link #setModel(ResultCollector)} methods for 75 * setting and getting the model class that handles the receiving and logging of 76 * sample results.</li> 77 * </ul> 78 * </li> 79 * </ul> 80 * For most developers, making a new visualizer is primarly for the purpose of 81 * either calculating new statistics on the sample results that other 82 * visualizers don't calculate, or displaying the results visually in a new and 83 * interesting way. Making a new visualizer for either of these purposes is easy - 84 * just extend this class and implement the 85 * {@link org.apache.jmeter.visualizers.Visualizer#add add(SampleResult)} 86 * method and display the results as you see fit. This AbstractVisualizer and 87 * the default 88 * {@link org.apache.jmeter.reporters.ResultCollector ResultCollector} handle 89 * logging and registering to receive SampleEvents for you - all you need to do 90 * is include the JPanel created by makeTitlePanel somewhere in your gui to 91 * allow users set the log file. 92 * <p> 93 * If you are doing more than that, you may need to extend 94 * {@link org.apache.jmeter.reporters.ResultCollector ResultCollector} as well 95 * and modify the {@link #configure(TestElement)}, 96 * {@link #modifyTestElement(TestElement)}, and {@link #createTestElement()} 97 * methods to create and modify your alternate ResultCollector. For an example 98 * of this, see the 99 * {@link org.apache.jmeter.visualizers.MailerVisualizer MailerVisualizer}. 100 * <p> 101 * 102 */ 103 public abstract class AbstractVisualizer 104 extends AbstractListenerGui 105 implements Visualizer, ChangeListener, UnsharedComponent, Clearable 106 { 107 /** Logging. */ 108 private static final Logger log = LoggingManager.getLoggerForClass(); 109 110 /** A panel allowing results to be saved. */ 111 private FilePanel filePanel; 112 113 /** A checkbox choosing whether or not only errors should be logged. */ 114 private JCheckBox errorLogging; 115 116 /* A checkbox choosing whether or not only successes should be logged. */ 117 private JCheckBox successOnlyLogging; 118 119 private JButton saveConfigButton; 120 121 protected ResultCollector collector = new ResultCollector(); 122 123 protected boolean isStats = false; 124 125 public AbstractVisualizer() { 126 super(); 127 128 // errorLogging and successOnlyLogging are mutually exclusive 129 errorLogging = new JCheckBox(JMeterUtils.getResString("log_errors_only")); // $NON-NLS-1$ 130 errorLogging.addActionListener(new ActionListener(){ 131 public void actionPerformed(ActionEvent e) { 132 if (errorLogging.isSelected()) { 133 successOnlyLogging.setSelected(false); 134 } 135 } 136 }); 137 successOnlyLogging = new JCheckBox(JMeterUtils.getResString("log_success_only")); // $NON-NLS-1$ 138 successOnlyLogging.addActionListener(new ActionListener(){ 139 public void actionPerformed(ActionEvent e) { 140 if (successOnlyLogging.isSelected()) { 141 errorLogging.setSelected(false); 142 } 143 } 144 }); 145 saveConfigButton = new JButton(JMeterUtils.getResString("config_save_settings")); // $NON-NLS-1$ 146 saveConfigButton.addActionListener(new ActionListener() { 147 public void actionPerformed(ActionEvent e) { 148 SavePropertyDialog d = new SavePropertyDialog( 149 GuiPackage.getInstance().getMainFrame(), 150 JMeterUtils.getResString("sample_result_save_configuration"), // $NON-NLS-1$ 151 true, collector.getSaveConfig()); 152 d.pack(); 153 ComponentUtil.centerComponentInComponent(GuiPackage.getInstance().getMainFrame(), d); 154 d.setVisible(true); 155 } 156 }); 157 158 filePanel = new FilePanel(JMeterUtils.getResString("file_visualizer_output_file"), ".jtl"); // $NON-NLS-1$ $NON-NLS-2$ 159 filePanel.addChangeListener(this); 160 filePanel.add(new JLabel(JMeterUtils.getResString("log_only"))); // $NON-NLS-1$ 161 filePanel.add(errorLogging); 162 filePanel.add(successOnlyLogging); 163 filePanel.add(saveConfigButton); 164 165 } 166 167 public boolean isStats() { 168 return isStats; 169 } 170 171 /** 172 * Gets the checkbox which selects whether or not only errors should be 173 * logged. Subclasses don't normally need to worry about this checkbox, 174 * because it is automatically added to the GUI in {@link #makeTitlePanel()}, 175 * and the behavior is handled in this base class. 176 * 177 * @return the error logging checkbox 178 */ 179 protected JCheckBox getErrorLoggingCheckbox() { 180 return errorLogging; 181 } 182 183 /** 184 * Provides access to the ResultCollector model class for extending 185 * implementations. Using this method and setModel(ResultCollector) is only 186 * necessary if your visualizer requires a differently behaving 187 * ResultCollector. Using these methods will allow maximum reuse of the 188 * methods provided by AbstractVisualizer in this event. 189 */ 190 protected ResultCollector getModel() { 191 return collector; 192 } 193 194 /** 195 * Gets the file panel which allows the user to save results to a file. 196 * Subclasses don't normally need to worry about this panel, because it is 197 * automatically added to the GUI in {@link #makeTitlePanel()}, and the 198 * behavior is handled in this base class. 199 * 200 * @return the file panel allowing users to save results 201 */ 202 protected Component getFilePanel() { 203 return filePanel; 204 } 205 206 /** 207 * Sets the filename which results will be saved to. This will set the 208 * filename in the FilePanel. Subclasses don't normally need to call this 209 * method, because configuration of the FilePanel is handled in this base 210 * class. 211 * 212 * @param filename 213 * the new filename 214 * 215 * @see #getFilePanel() 216 */ 217 public void setFile(String filename) { 218 // TODO: Does this method need to be public? It isn't currently 219 // called outside of this class. 220 filePanel.setFilename(filename); 221 } 222 223 /** 224 * Gets the filename which has been entered in the FilePanel. Subclasses 225 * don't normally need to call this method, because configuration of the 226 * FilePanel is handled in this base class. 227 * 228 * @return the current filename 229 * 230 * @see #getFilePanel() 231 */ 232 public String getFile() { 233 // TODO: Does this method need to be public? It isn't currently 234 // called outside of this class. 235 return filePanel.getFilename(); 236 } 237 238 /** 239 * Invoked when the target of the listener has changed its state. This 240 * implementation assumes that the target is the FilePanel, and will update 241 * the result collector for the new filename. 242 * 243 * @param e 244 * the event that has occurred 245 */ 246 public void stateChanged(ChangeEvent e) { 247 log.debug("getting new collector"); 248 collector = (ResultCollector) createTestElement(); 249 collector.loadExistingFile(); 250 } 251 252 /* Implements JMeterGUIComponent.createTestElement() */ 253 public TestElement createTestElement() { 254 if (collector == null) { 255 collector = new ResultCollector(); 256 } 257 modifyTestElement(collector); 258 return (TestElement) collector.clone(); 259 } 260 261 /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ 262 public void modifyTestElement(TestElement c) { 263 configureTestElement((AbstractListenerElement) c); 264 if (c instanceof ResultCollector) { 265 ResultCollector rc = (ResultCollector) c; 266 rc.setErrorLogging(errorLogging.isSelected()); 267 rc.setSuccessOnlyLogging(successOnlyLogging.isSelected()); 268 rc.setFilename(getFile()); 269 collector = rc; 270 } 271 } 272 273 /* Overrides AbstractJMeterGuiComponent.configure(TestElement) */ 274 public void configure(TestElement el) { 275 super.configure(el); 276 setFile(el.getPropertyAsString(ResultCollector.FILENAME)); 277 ResultCollector rc = (ResultCollector) el; 278 errorLogging.setSelected(rc.isErrorLogging()); 279 successOnlyLogging.setSelected(rc.isSuccessOnlyLogging()); 280 if (collector == null) { 281 collector = new ResultCollector(); 282 } 283 collector.setSaveConfig((SampleSaveConfiguration) rc.getSaveConfig().clone()); 284 } 285 286 /** 287 * This provides a convenience for extenders when they implement the 288 * {@link org.apache.jmeter.gui.JMeterGUIComponent#createTestElement()} 289 * method. This method will set the name, gui class, and test class for the 290 * created Test Element. It should be called by every extending class when 291 * creating Test Elements, as that will best assure consistent behavior. 292 * 293 * @param mc 294 * the TestElement being created. 295 */ 296 protected void configureTestElement(AbstractListenerElement mc) { 297 // TODO: Should the method signature of this method be changed to 298 // match the super-implementation (using a TestElement parameter 299 // instead of AbstractListenerElement)? This would require an 300 // instanceof check before adding the listener (below), but would 301 // also make the behavior a bit more obvious for sub-classes -- the 302 // Java rules dealing with this situation aren't always intuitive, 303 // and a subclass may think it is calling this version of the method 304 // when it is really calling the superclass version instead. 305 super.configureTestElement(mc); 306 mc.setListener(this); 307 } 308 309 /** 310 * Create a standard title section for JMeter components. This includes the 311 * title for the component and the Name Panel allowing the user to change 312 * the name for the component. The AbstractVisualizer also adds the 313 * FilePanel allowing the user to save the results, and the error logging 314 * checkbox, allowing the user to choose whether or not only errors should 315 * be logged. 316 * <p> 317 * This method is typically added to the top of the component at the 318 * beginning of the component's init method. 319 * 320 * @return a panel containing the component title, name panel, file panel, 321 * and error logging checkbox 322 */ 323 protected Container makeTitlePanel() { 324 Container panel = super.makeTitlePanel(); 325 // Note: the file panel already includes the error logging checkbox, 326 // so we don't have to add it explicitly. 327 panel.add(getFilePanel()); 328 return panel; 329 } 330 331 /** 332 * Provides extending classes the opportunity to set the ResultCollector 333 * model for the Visualizer. This is useful to allow maximum reuse of the 334 * methods from AbstractVisualizer. 335 * 336 * @param collector 337 */ 338 protected void setModel(ResultCollector collector) { 339 this.collector = collector; 340 } 341 342 public void clearGui(){ 343 super.clearGui(); 344 filePanel.clearGui(); 345 } 346 }