diff --git a/xdocs/extending/index.xml b/xdocs/extending/index.xml index a1a0893625..a9935dcc8c 100644 --- a/xdocs/extending/index.xml +++ b/xdocs/extending/index.xml @@ -18,8 +18,8 @@ to make this task easier.
The timer interface:
@@ -95,6 +95,313 @@ on their own), and you may have to do casting and parsing. Example: an Integer have to be converted from a String to an int, so your getXXX() method should check for this possibility to avoid exceptions. +The SamplerController interface looks as follows:
++ Entry nextEntry(); + Collection getListeners(); + void addSamplerController(SamplerController controller); + void addConfigElement(ConfigElement config); + Object clone(); ++
Again, clone() is a method that must be implemented to all SamplerControllers to avoid +contamination between sampling threads.
+The nextEntry() method is the essential job of a SamplerController - to deliver +Entry objects to be sampled. An Entry object encapsulates all the information needed +by a Sampler to do its job. The nextEntry() method should work like an iterator and +continuously return new Entry objects. +
+There are two boundary conditions that need to be handled. If the Controller has no +more Entries to give, for the rest of the test, it should return null. Therefore, +if your Controller has sub-controllers it is receiving Entries from, it should remove +them from its list of controllers to get Entries from. The other condition is when +your controller reaches the end of its list of Entries, and it needs to start over +from the beginning. The parent Controller needs to know this so that it can move +on to its next controller in its list. Therefore, at the end of each iteration, +your SamplerController needs to return a CycleEntry object instead of a normal Entry. +Conversely, this means that if your Controller receives a CycleEntry object, it should +move on to the next Controller in its list.
+A logic controller does not generate Entries on its own, but simply regulates +the flow of Entries from its sub-controllers. A logic controller might provide +looping logic, or it might modify the Entries that pass through it, or whatever. +GenericController provides an implementation that does absolutely nothing but +pass Entries on from its sub-controllers. This class is useful both for reference +purposes and to extend, since it provides a lot of methods you're likely to find +useful +
+getListeners() is an odd member of this Class. It's there to serve those who +want their controller to receive sample data. This would be useful for a controller +that modified Entry objects based on previous sample results (like an HTML spider +that dynamically reacted to previously sampled webpages for links and forms). The +responsibility of the controller implementer is to collect all potential listeners +from the sub-controller list, and add themselves if desired. Most SamplerControllers +that extend GenericController don't have to do anything.
+addSamplerController(SamplerController controller) is the method used to +add sub controllers to your SamplerController.
+addConfigElement(ConfigElement config) Your SamplerController should also +be capable of holding configuration elements and adding them to Entries as they +pass through your controller. Again, see GenericController for reference. Essentially, +all Entry objects that get returned by nextEntry() are handed all the ConfigElements +of the controller. +
+A SamplerController that generates Entry objects is just like a logic controller +except that it creates its own Entry objects instead of gathering them from +sub-controllers (although, to be fully correct, your test sample SampleController +should handle both possibilities). Your test sample SampleController can also +benefit from extending GenericController. By doing so, most of your cloning and +saving needs are handled (but probably not entirely). See HttpTestSample as +reference.
+The Sampler interface:
++ public SampleResult sample(Entry e) ++
Your Sampler has two responsibilities. Of lesser importance, it should do whatever +it is you want to do, given an Entry object that hopefully contains information +about what is to be sampled. Of greater importance, your sampler should return +a SampleResult object that holds information about the sampling. Information such +as how long the sample took, the text response from the sample (if appropriate), and +a string that describes the location of what was sampled. The SampleResult interface +is essentially a Map with public static Strings as keys.
+In order to take part in the JMeter UI, your component needs to implement the +JMeterComponentModel interface:
++ Class getGuiClass(); + public String getName(); + public void setName(String name); + public Collection getAddList(); + public String getClassLabel(); + public void uncompile(); ++
Most of this stuff is easy, boring, and tedious. getName(), setName() is a simple +String property that is the name of the object. getClassLabel() should return +a String that describes the class. This string will be displayed to the user and +so should be short but meaningful. getGuiClass() should return a Class object for +the class that will be used as a GUI component. This class should be a subclass +of java.awt.Container, and preferably a subclass of javax.swing.JComponent.
+getAddList() should return a list of either Strings or JMenus. These Strings +represent the Classes that can be added to your SamplerController. Each String +should correspond to the target class's getClassLabel() String. MenuFactory is +a class that will return some preset menu lists (such as all available SamplerControllers, +all available ConfigElements, etc).
+uncompile is a cleanup method used between sampling runs. When the user +hits "Start", JMeter "compiles" the objects in the tree. Child nodes are added +to their parent objects recursively until there is one TestPlan object, which is +then submitted for testing. Afterward, these elements have to un-added from their +parent objects, or uncompiled. To uncompile your class, simply clear all your +data structures that are holding sub-elements. For your SamplerController, this +will be the list of sub-controllers and the list of ConfigElements.
+That's it, except for your GUI class. If your SamplerController has no +configuration needs, just return org.apache.jmeter.gui.NamePanel, and the user will +at least be able to change the name of your component. Otherwise, create a gui class +that implements the ModelSupported interface:
++ void setModel(Object model); + public void updateGui(); ++
setModel is used to hand your JMeterModelComponent class to the GUI class when +it is instantiated. It is your responsibility for providing the means by which +the Gui class updates the values in the model class. For updating in the other +direction, there is updateGui(), which the model class can call if necessary. +Note, normally, this call is made for you automatically whenever the Gui is brought +to the screen. If you are creating a Visualizer, then you may need to use updateGui(). +For reference, refer to UrlConfigGui (in org.apache.jmeter.protocol.http.config.gui).
+If you have done all this correctly, there's just one more step. If you compile +your classes into the ApacheJMeter.jar file, then you're done. Your classes will +be automatically found and used. Otherwise, you will need to modify jmeter.properties. +The search_paths property should be modified to include the path where your +classes are. This does not obviate the need for your classes to be in the JVM's +CLASSPATH - it is an additional requirement. Otherwise, your classes will be +detected, and the Gui will not make them available to the user.
+The Saveable interface has just one method:
++ public Class getTagHandlerClass() ++
This method simply returns the Class object that represents the Class that handles +the saving and loading of your component.
+To write this SaveHandler, make a class that extends TagHandler +(from org.apache.jmeter.save.xml). Note, if your component extends AbstractConfigElement, +you don't need to do this - provided you only need to save information stored in +the Map from AbstractConfigElement.
+To write your own TagHandler, you will have to implement the following methods:
++ public abstract void setAtts(Attributes atts) throws Exception + public String getPrimaryTagName() + public void save(Saveable objectToSave,Writer out) throws IOException ++
getPrimaryTagName() should return the String that is the XML tagname that your +class handles. When you save your object, it should all be contained within an +XML tag of the same name. This will ensure that when JMeter's parser hits that tag, +your class will be called upon to handle the data.
+setAtts(Attributes atts) is called when the parser first hits your tag. +If this primary tag has any attributes, this method represents your chance to save +the information.
+save(Saveable objectToSave,Writer out) - when the user selects "Save", +JMeter will call this method and hand the Saveable object to be saved (it will be +the object that specified your TagHandler as the class responsible for it's saving). +This method should use the given Writer object to print all the XML necessary to +save the current state of the objectToSave.
+There's more you have to do to handle creating a new Object when JMeter parses +an XML file. However, there's no standard interface you need to implement, but rather, +JMeter uses reflection to generate method calls into your class. When JMeter hits +a tag that corresponds to your PrimaryTagName, an instance of your TagHandler will +be created, and it's setAtts() method will get called. Thereafter, methods are called +depending on subsequent tags and character data. For every tag, JMeter calls +<tag-name>TagStart(Attributes atts), and for every end tag, JMeter calls +<tag-name>TagEnd().
+Additionally, JMeter will call a method that corresponds to all tags that are +current. So, for instance, if JMeter runs into a tag name "foo", then +foo(Attributes atts) will be called. If JMeter then parses character data, +then foo(String data) will be called. If JMeter parses a tag within foo, called +"nestedFoo", then JMeter will call foo_nestedFoo(Attributes atts) and +foo_nestedFoo(String data). And so on. +
+An annotated example:
+
+public class AbstractConfigElementHandler extends TagHandler
+{
+ private AbstractConfigElement config;
+ private String currentProperty;
+
+ public AbstractConfigElementHandler()
+ {
+ }
+
+ /**
+ * Returns the AbstractConfigElement object parsed from the XML. This method
+ * is required to fulfill the SaveHandler interface. It is used by the XML
+ * routines to gather all the saved objects.
+ */
+ public Object getModel()
+ {
+ return config;
+ }
+
+ /**
+ * This is called when a tag is first encountered for this handler class to handle.
+ * The attributes of the tag are passed, and the SaveHandler object is expected
+ * to instantiate a new object.
+ */
+ public void setAtts(Attributes atts) throws Exception
+ {
+ String className = atts.getValue("type");
+ config = (AbstractConfigElement)Class.forName(className).newInstance();
+ }
+
+ /**
+ * Called by reflection when a <property> tag is encountered. Again, the
+ * attributes are passed.
+ */
+ public void property(Attributes atts)
+ {
+ currentProperty = atts.getValue("name");
+ }
+
+ /**
+ * Called by reflection when text between the begin and end <property>
+ * tag is encountered.
+ */
+ public void property(String data)
+ {
+
+ if(data != null && data.trim().length() > 0)
+ {
+ config.putProperty(currentProperty,data);
+ currentProperty = null;
+ }
+ }
+
+ /**
+ * Called by reflection when the <property> tag is ended.
+ */
+ public void propertyTagEnd()
+ {
+ // Here's a tricky bit. See below for explanation.
+ List children = xmlParent.takeChildObjects(this);
+ if(children.size() == 1)
+ {
+ config.putProperty(currentProperty,((TagHandler)children.get(0)).getModel());
+ }
+ }
+
+
+ /**
+ * Gets the tag name that will trigger the use of this object's TagHandler.
+ */
+ public String getPrimaryTagName()
+ {
+ return "ConfigElement";
+ }
+
+ /**
+ * Tells the object to save itself to the given output stream.
+ */
+ public void save(Saveable obj,Writer out) throws IOException
+ {
+ AbstractConfigElement saved = (AbstractConfigElement)obj;
+ out.write("<ConfigElement type=\"");
+ out.write(saved.getClass().getName());
+ out.write("\">\n");
+ Iterator iter = saved.getPropertyNames().iterator();
+ while (iter.hasNext())
+ {
+ String key = (String)iter.next();
+ Object value = saved.getProperty(key);
+ writeProperty(out,key,value);
+ }
+ out.write(</ConfigElement>");
+ }
+
+ /**
+ * Routine to write each property to xml.
+ */
+ private void writeProperty(Writer out,String key,Object value) throws IOException
+ {
+ out.write("<property name=\"");
+ out.write(key);
+ out.write("\">\n");
+ JMeterHandler.writeObject(value,out);
+ out.write("\n</property>\n");
+ }
+
++In the propertyTagEnd method, takeChildObjects() is called on the xmlParent +instance variable. xmlParent is inherited from TagHandler. It is the DocumentHandler +object that is running the show. xmlParent takes an XML file that represents a portion of +the test configuration tree, and recreates a tree-like data structure. When it is +done, it will convert its tree-like data structure into the test configuration tree +structure. +
+However, sometimes, a tree element has sub objects that you do not want represented +in the tree - rather, they are objects that are part of your object. But, they may +be complicated enough to warrant their own SaveHandler class, and thus, the xmlParent +picks them up as part of its tree. When the tag is done, and you know that there are +child objects you want to grab, you can call the "takeChildObjects" method and get a +List object containing them all. This will remove them from the tree, and you can add +them to your object that you're creating. +
++UrlConfig is good example. It extends AbstractConfigElement, so it uses exactly the +code above to save and reload itself from XML. However, one of the pieces of data +that UrlConfig stores is an Arguments object. Arguments is too complicated to save +to file as a simple string, so it has its own Handler object (ArgumentsHandler). In +the above code, when the call to JMeterHandler.writeObject(value,out) is made, the +writeObject method detects whether the object implements Saveable, and if so, calls +the object's SaveHandler class to handle saving it. This means, however, that when +reading that XML file, the Argument object will show up as a separate entity in +the data tree, whereas it originally was just part of the data of the UrlConfig +object. In order to preserve that relationship, it's necessary for the +AbstractConfigElementHandler to check after each property tag is done for child +objects in the tree, and take them for its own use. +
++Please study the other SaveHandler objects and the TagHandler class to learn more +about how saving is accomplished. Once you understand the design, writing your +own SaveHandler is very easy. +