User Documentation
One Minute Introduction
Two Minute Introduction
Your own plugin
Coffee Break Introduction
Property substitution
FAQ
|
|||||
|
|||||
User DocumentationOne Minute Introduction |
Steps for writing Custom plugins
Steps for writing Custom pluginsAs a XDoclet plugin developer, you will have to develop the following artifacts
The template should be present in the same package and the same name as the plugin class, and is likely to have a .vm or .jelly extension depending upon whether you are trying to generate java code or an xml file (descriptor may be) file respectively. A Simple Case for code generationIn one of the projects I previously worked on, business logic was encapsulated in command objects that were maintained in a command factory. The command factory would source it contents from an XML file (command-mapping.xml) that would maintain the 'command name command implementation class' mapping. So everytime somebody added a command to the source control, they also had to make sure that XML file is updated. Even if developers made sure that the XML and the implementation classes are in sync, a typo somewhere could result in errors and worse it wouldn't show up until the code is actually run. This is a typical case of duplication of information and XDoclet has been designed to solve such problems. If the command name (metadata) can be specified in the command class itself, XDoclet would take care of generating the XML file. Ofcourse, it would mean developing custom doclet tags to specify the metadata (command name) in the source file, a template that generates the required file and a plugin that ties everything together. The following section discusses the custom doclet tags that can be used to add metadata to the Command implementation classes. Developing custom XDoclet tagsA Java Command interface specifies the contract for all concrete commands to implement. 'GetPurchaseOrderCommand' is one such implementation. It uses the @command.class tag with an attribute 'name' to specify its name. The 'command.class' tag could be expanded in the future to support more attributes. You might wonder that the '.class' part of the tag name could have very well been omitted. But consider this - currently we are accepting 'class' level tags. In the future we might want to specify metadata at method level too and the tag naming w'd'nt look too intuitive if the intent did'nt reflect in the tag name itself (we can have @command.method later). Command.java package org.xdoclet.plugin.commands /** * * * @command.class generate=false * */ public interface Command{ public void execute(Object obj); } GetPurchaseOrderCommand.java package org.xdoclet.plugin.commands; /** * * @command.class name="GetPurchaseOrder" * */ public class GetPurchaseOrderCommand implements Command{ public void execute(Object obj){ //business logic to execute retrieve Purchase Order } } Now that we know how our tag needs to look like, the next logical step would be to describe the same. It is typically specified through an interface that extends QDox DocletTag. The interface name is obtained by appending "Tag" to the desired tag name. A tag named 'command' translates to interface CommandTag while 'command.class' translates to CommandClassTag inteface. Note that java bean method naming conventions are used to specify the tag attributes. A Tag with attributes foo, bar are specified through methods getFoo and getBar on the interface respectively. Because of an internal naming conflict, tag attribute 'name' has to be specified with a trailing underscore : getName_()! We will see later why we need the isGenerate method. CommandClassTag.java package org.xdoclet.plugin.command.qtags; import com.thoughtworks.qdox.model.DocletTag; /** * This is a Custom Tag * * @qtags.location class * @qtags.once * */ public interface CommandClassTag extends DocletTag { /** * @qtags.required */ String getName_(); /** * A Marker tag that indicates whether the source file * needs to be processed or not */ /** * @qtags.default true */ String isGenerate(); } The above code snippet indicates that the tag specification itself is carrying some meta information in the form of @qtags.xxxx tags! @qtags.location class - This specifies that the command.class tag can be specified at class level only. The other possible values are @qtags.once - This allows us to specify the tag cardinality - that the command.class tag can be specified only once in the source @qtags.required - that 'name' is a required attribute and must be specified when using the command.class tag. Calling the custom tag that we just developed, a XDoclet tag is probably a misnomer. They are in fact QDox DocletTags. We are just creating another Javadoc like @tags for QDox to recognize when parsing the source file. We developed the interface but somebody has to provide an implementation. The tool does it for us through another XDoclet code generation plugin!. We just need to invoke the QTagImplPlugin from the build file. <component classname="org.xdoclet.plugin.qtags.impl.QTagImplPlugin" destdir="src/java"/>
This instructs XDoclet to generate the concrete tag implementation classes for the Tag interfaces we developed. Now that we are done with developing custom tags, it is probably a good time to discuss another area where XDoclet2 improves upon the earlier version - tag validation. Tag validationA tag definition can specify the following -
XDoclet2 greatly improves over XDoclet 1.x in the area of tag validation. XDoclet 1.x does not warn you
Eg : Specifying the following on a bean method does not make much sense CustomerBean.java public abstract class CustomerBean extends EntityBean{ //.. @ejb.bean name="CustomerBean" public abstract String getName(); //.. Note that it definitely makes sense at the bean 'class' level. Unfortunately XDoclet 1.x does not provide any feedback to the user regarding this error. It basically lacks a tag validation feature. XDoclet2 addresses this issue though. As we saw earlier, XDoclet often tends to eats its own dog food _ It generated the TagLibrary class for us by looking at our custom tag definitions. It also generated the tag validation routine transparently to the plugin developer. It translates all the validation rules we specified in the Tag interface into methods in the implementation class. QDox invokes these methods at the time of extracting the meta information (specified through @tags) from our source files. At this stage we have developed the custom tag interface and xdoclet even generated the implementation classes for that. QDox needs to be made aware of it and the following section tells us how. How to let XDoclet know of the custom tagsQDox understands and parses java source file retaining the precious metadata in the process. So how does QDox know that you have just developed a custom tag and that it needs to look for that tag as well in the source file? - Well, its not magic ( we have already seen enough ) and it does'nt, unless ofcourse you register the tag with Metadata provider's DocletFactory. Luckily, registering the tag is a trivial task. Constructor of custom CommandPlugin that we just developed indicates that the class has dependencies on
Generama by default supports Jelly, Velocity and Freemarker for template scripting by providing respective TemplateEngine implementations _ JellyTemplateEngine, VelocityTemplateEngine and FreeMarkerTemplateEngine. Atleast for now, it looks like XDoclet2 is being used only for java/j2ee stack code generation. Generama recognizes this - it provides and registers QDoxCapableMetadataProvider with the DI container (Pico) by default and when it starts up, Pico does what it has been designed to do - wiring up dependencies. So now that we have access to the MetaDataProvider, letting the provider know about our custom tags is simple. QDox Provider maintains a DocletFactory where it holds on to all the tag definitions. Registering the new tag with the DocletFactory can be done as follows. It simply binds the name of the Tag against the tag implementation class. CommandPlugin.java public class CommandPlugin extends QDoxPlugin { public CommandPlugin(JellyTemplateEngine jellyTemplateEngine, QDoxCapableMetadataProvider metadataProvider,WriterMapper writerMapper) throws ClassNotFoundException { //... //.. //register a tag with the given name (command) and implementation class metadataProvider.getDocletTagFactory().registerTag("command.class", org.xdoclet.plugin.command.qtags.CommandClassTagImpl.class); } } So far this looks good. A cursory look at the EJB Plugin documentation (or for that matter Hibernate plugin documentation) will reveal that it supports a multitude of tags. As of today it supports close to 31 tags! Registering each of these one by one could quickly become tedious and redundant, worse we might even forget to register a tag with QDox, thereby losing out on the metadata specified by that tag. Would'nt it be good if we could just specify the tag interface and somehow the Provider gets to know about it automatically? It is anyways redundant code that nobody wants to write. Relax, we got help here. XDoclet does it for us...almost. The following snippet from the build file registers the QTagLibraryPlugin utility with Generama. This component looks at all the DocletTag in the specified directory and generates a TagLibrary class. As the name indicates this class acts as a 'library' of DocletTags associated with a plugin and registers them with the provider. <target name="gen.qtags.impl"> <property name="xdoclet.qtags.namespace" value="command"/> <xdoclet> <fileset dir="src"> <include name="**/*.java"/> </fileset> <component classname="org.xdoclet.plugin.qtags.impl.QTagImplPlugin" destdir="${basedir}/src" /> <component classname="org.xdoclet.plugin.qtags.impl.QTagLibraryPlugin" destdir="${basedir}/src" packagereplace="org.xdoclet.plugin.${xdoclet.qtags.namespace}.qtags" /> </target> TagLibrary.java public class TagLibrary{ // The Constructor handles the job of registering the tags with the MetadataProvider public TagLibrary(MetadataProvider metadataProvider){ metadataProvider.getDocletTagFactory ().registerTag ("dao.call", org.xdoclet.plugin.ejb.qtags.DaoCallTagImpl.class); metadataProvider.getDocletTagFactory ().registerTag ("ejb.aggregate", org.xdoclet.plugin.ejb.qtags.EjbAggregateTagImpl.class); metadataProvider.getDocletTagFactory ().registerTag ("ejb.bean", org.xdoclet.plugin.ejb.qtags.EjbBeanTagImpl.class); metadataProvider.getDocletTagFactory ().registerTag ("ejb.create-method", org.xdoclet.plugin.ejb.qtags.EjbCreateMethodTagImpl.class); //.. //.. You are just expected to instantiate this class from within your plugin. I guess that is not too much to ask given the amount of redundant work XDoclet has already handled for us. But it does look a little out of place in the code where it is used (a hanging reference). ACustomPlugin public class ACustomPlugin extends QDoxPlugin { public ACustomPlugin(JellyTemplateEngine jellyTemplateEngine, QDoxCapableMetadataProvider metadataProvider, WriterMapper writerMapper) throws ClassNotFoundException { //Custom Plugin Code... //Does'nt look nice, does it? new TagLibrary (metadataProvider); } } Developing a Custom PluginCommandPlugin.java package org.xdoclet.plugin.command; import java.util.Collection; import java.util.*; import org.generama.JellyTemplateEngine; import org.generama.QDoxCapableMetadataProvider; import org.generama.WriterMapper; import org.generama.defaults.QDoxPlugin; import com.thoughtworks.qdox.model.JavaClass; import com.thoughtworks.qdox.model.DocletTag; import org.xdoclet.plugin.command.qtags.TagLibrary; public class CommandPlugin extends QDoxPlugin { public CommandPlugin(JellyTemplateEngine jellyTemplateEngine, QDoxCapableMetadataProvider metadataProvider, WriterMapper writerMapper) throws ClassNotFoundException { super (jellyTemplateEngine, metadataProvider, writerMapper); setFilereplace ("command-mapping.xml"); setMultioutput (false); /*The following line of code is a programmatic way of registering the tags*/ /*metadataProvider.getDocletTagFactory ().registerTag ("command.class", org.xdoclet.plugin.command.qtags.CommandClassTagImpl.class);*/ /* xdoclet2 recommended way of registering custom tags*/ new TagLibrary(metadataProvider); } /** * A helper method on the Plugin class that fetches the name of the command. * */ public String getCommandName(Object metadata){ JavaClass javaClass = (JavaClass) metadata; return javaClass.getNamedParameter("command.class","name"); } // Checks if the source file is a Command interface and that you do // want to consider it for code generation. public boolean shouldGenerate(Object metadata) { JavaClass javaClass = (JavaClass) metadata; boolean isCommand = javaClass.isA("org.xdoclet.plugin.commands.Command"); boolean ignore = "false".equals(javaClass.getNamedParameter("command.class","generate")); if (isCommand && !ignore) return true; else return false; } } Now that we are done with developing the tags and the plugin, the next logical step would be to write the template. Since the result of the generation needs to be an XML file, as per recommendation, jelly template is being employed. CommandPlugin.jelly <j:jelly xmlns:j="jelly:core" xmlns:x="jelly:xml"> <x:comment text="${dontedit}" trim="true"/> <commands> <j:forEach var="class" items="${metadata}"> <j:if test="${plugin.shouldGenerate(class)}"> <command name="${plugin.getCommandName(class)}" value="${class.fullyQualifiedName}"/> </j:if> </j:forEach> </commands> </j:jelly> This results in a file 'command-mapping.xml' : <?xml version="1.0" encoding="ISO-8859-1"?> <!--Generated file. Do not edit.--> <commands> <command value="org.xdoclet.plugin.commands.GetPurchaseOrderCommand" name="GetPurchaseOrder"/> <command value="org.xdoclet.plugin.commands.SavePurchaseOrderCommand" name="SavePurchaseOrder"/> <command value="org.xdoclet.plugin.commands.UpdatePurchaseOrderCommand" name="UpdatePurchaseOrder"/> </commands> I had 3 Command implementations in my file system and the generated XML file reflects that. Command.java was also present in the same package as the implementation files and would have showed up in the XML file had we not added the attribute 'generate=false' at the class level. The plugin accordingly ingored that file. (see shouldGenerate() method). Where did this comment come from in the source file ? <!--Generated file. Do not edit.-->
Remember abstract Plugin class mapped a String to 'dontedit' key and added it to the template context? the template used it for displaying the comment. //TODO - Internationalizing comments through externalizer plugin. This plugin always results in a file named command-mapping.xml since it is harcoded in the CommandPlugin class constructor. This file name can be overriden at run time by specifying it as the value of filereplace attribute in the <component> element . The code for the Command plugin that we developed can be downloaded here. In order to build it using maven, it needs to be unzipped it to the same location as other XDoclet2 plugins (source). It is packaged as a xdoclet2 maven multiproject and can be built like any other plugin. For eg on my machine, the following builds the command-plugin $ cd /home/lab/XDoclet2/xdoclet-plugins-1.0.2/src $ cd plugin-command $ maven jar Java Code Generation ExampleWe looked at an example of XML code generation that results in a single file. Lets look at a pretty basic java code generation example that generates multiple java source files. I put this together quickly while addressing a query at javaranch. problem: Automatically generate JUnit test case stubs for all public methods in a given set of java source files. Note that even though this can be done very easily using XDoclet2 , the tool, generally is not used to address such problem domains. Why? - generating unit test cases stub is something that you would want to do only once. Its a typical case of Passive code generation wherein you are more likely to edit the generated stub. XDoclet2 is an 'active code generation' tool. The end product of code generation is unlikely to be edited when using XDoclet2. The plugin required to solve the above problem is shown below. We are expected to extend org.generama.defaults.JavaGeneratingPlugin and use VelocityTemplateEngine (You can use Freemarker / groovy template as well ) for java code generation. JUnitGeneratorPlugin.java package com.xdoclet2tutorial.plugin.command; import org.generama.VelocityTemplateEngine; import org.generama.QDoxCapableMetadataProvider; import org.generama.WriterMapper; import org.generama.defaults.JavaGeneratingPlugin; public class JUnitGeneratorPlugin extends JavaGeneratingPlugin { public JUnitGeneratorPlugin(VelocityTemplateEngine velocityTemplateEngine, QDoxCapableMetadataProvider metadataProvider, WriterMapper writerMapper) throws ClassNotFoundException { super(velocityTemplateEngine, metadataProvider, writerMapper); //We will end up with mutiple java source files ( one TestCase class per java class). setMultioutput(true); } } The corresponding template is shown below. The velocity template essentially iterates through all methods in a given class , makes sure that its accessibility modifier is 'public' and generates the test method stub for that method. The corresponding test case class name is derived as per Junit naming convention (by appending 'Test' to the class name). JUnitGeneratorPlugin.vm // ${dontedit} #set( $class = $metadata ) package ${plugin.getDestinationPackage($class)}; import org.junit.TestCase; public class ${class.getName()}Test extends TestCase{ public ${class.getName()}Test(String name){ super(name); } public void setUp(){ } #foreach( $method in $class.getMethods()) #if($method.isPublic()) public void test${method.getName()}(){ } #end #end public void tearDown(){ } } and the build file snippet to invoke the plugin: <target name="junit.stub.generate"> <xdoclet> <!-- defines the file handled by xdoclet2 --> <fileset dir="src/main/java"> <include name="com/xdoclet2tutorial/command/*.java"/> </fileset> <!-- uses java regex 'groups' feature to determine the name of the destination file --> <component classname="com.xdoclet2tutorial.plugin.command.JUnitGeneratorPlugin" destdir="${gen-src-dir}" fileregex="(.*)(.java)" filereplace="$1Test.java"/> </xdoclet> </target> You might have probably noticed that the JUnitGeneratorPlugin template just requires the basic QDox services - Object representation of the java source file that it parses. In the future, XDoclet2 might probably come bundled with such a plugin that accepts the template location at build time. |
||||
|
Copyright 2003-2006 - The Codehaus. All rights reserved unless otherwise noted.
Powered by Atlassian Confluence
|
|||||