User Documentation
One Minute Introduction
Two Minute Introduction
Your own plugin
Coffee Break Introduction
Property substitution
FAQ
|
|||||
|
|||||
User DocumentationOne Minute Introduction |
'Aggregate' and 'Transformation' generation with XDoclet2
'Aggregate' and 'Transformation' generation with XDoclet2Certain templates need to work on metadata extracted from individual files - Generating EJB Local/Remote Home interfaces for the entity beans is an example. There is a one-one mapping between the source file and the generated file. This is termed as 'Transformation' generation. There are others that need a collection of files to work on - Generating ejb-jar.xml from all the entity beans present in the system is an example. This method is called 'Aggregate' generation wherein the plugin extracts data from multiple source files and generates one file in response. How does XDoclet framework know that a certain template needs to be invoked repeatedly with a different metadata each time while the other needs to be invoked only 'once' with a collection of metadata? Or in other words, how does XDoclet figure out if the code generation activity should result in a single file or multiple files? Ofcourse Xdoclet doesnt know this important piece of information unless explicitly specified. All plugins (concrete classes that extend abstract Plugin class) that do something useful will indicate this explicity through the setMultioutput() method of base Plugin Class. The Plugin class in turn invokes the template passing in the context/metadata information. public void setMultioutput(boolean multioutput) { isMultioutput = multioutput; } local/remote home generation plugin would set the 'isMultioutput' variable to 'true' while Following is a snippet from the start() method of Plugin class. By the time start() is called, Plugin class already has access to all the metadata. The abstract Plugin decides later as to what needs to be done with it by querying the multiOutput variable that is set by the concrete plugin class. We can see template method pattern at work here. abstract class Plugin implements .. { //.. //.. public void start(){ //... //... if (isMultioutput()) { //iterate through the metadata collection made available for (Iterator iterator = metadata.iterator(); iterator.hasNext();){ Object meta = (Object) iterator.next(); //let the plugin decide if it needs to process metadata if (shouldGenerate(meta)) { Outcome out = getWriterMapper().getOutcome(meta, this); if (out.getWriter() != null) { Map m = new HashMap(); //added the metadata against the key 'metadata'. //Now we know how velocity template gets access to the metadata //through $metadata variable. m.put("metadata", meta); populateContextMap(m); templateEngine.generate(out.getWriter(), m, getEncoding(), getClass()); // validate the generated output if the concrete plugin desires so if (validate && outputValidator != null) { outputValidator.validate(out.getURL()); } out.getWriter().close(); } } } } protected void populateContextMap(Map map) { map.put("class", map.get("metadata")); map.put("plugin", this); map.put("dontedit", Plugin.DONTEDIT); } } Also of interest is the method 'shouldGenerate'. The abstract Plugin class, before processing the metadata, provides an opportunity for the custom plugin to decide if it really wants to process (generate code) this metadata. //by default it returns true. public boolean shouldGenerate(Object metadata) { return true; } By default the methods returns 'true'. If not overridden, all source files as selected by the ant <file/> task will be processed by QDox and XDoclet will issue warnings on all those source files that carry tags whose specifications have not been defined or have not been registered with the DocletFactory. Custom plugins can inspect meta data and decide whether it needs to process the source (may be a Bean class) that is represented by this metadata. A typical example may be a entity bean class that you dont want to process. You can explicity indicate that by specifying the following metadata on the bean class. /** * @ejb.bean generate=false * */ abstract class PersonBean implements EntityBean{ //.. //.. } 'shouldGenerate' can then be overriden in the plugin class and a decision can be made based on the 'generate' attribute value obtained from the metadata object. QDox, also from codehaus, does the job of providing the metadata for the java class that it is processing. It acts as the metadata provider for code generation for java classes. How does the template gain access to the metadata object?'populateContextMap' method (see the above code snippet) registers the metadata and the plugin instance with velocity/jelly context against the keys "class" and "plugin" respectively. Infact, the metadata is also available under the key "metadata". Class ConcretePlugin extends Plugin{ //.. //.. protected void populateContextMap (Map context){ super.populateContextMap (context); populateContextMap ("utils", new Utility ()); } } The template can now access the Utility class directly through $utils variable. How does this help?The fact that you need access to metadata/context is pretty obvious - You cannot generate code without a context to work on. The biggest gain is that providing utility methods to the template is as simple as adding the same to the plugin class. For eg: Org.xdoclet.plugin.ejb.interfaces.LocalInterfacePlugin plugin is used for generating local interfaces for the entity beans. It allows access to EJbUtil helper class by providing an accessor on the plugin class. class LocalInterfacePlugin extends ..{ //.. //.. public EjbUtils getEjbUtils() { return ejbUtils; } } The velocity template in turn uses it as follows: File: LocalInterfacePlugin.vm ----------------------------- //$metadata variable being used by the template. $metadata represents //a com.thoughtworks.qdox.model.JavaClass instance #set( $class = $metadata ) #foreach ($method in $plugin.ejbUtils.getInterfaceMethods ($class, "local")) $method.getDeclarationSignature(false); #end ----------------------------- Note that we discussed another way of achieving the same in the previous section. Ok, what does the Plugin do if the code generation activity needs to result in one consolidated file (eg: ejb-jar.xml)? We looked at the 'if' block of the start () method of the abstract Plugin class earlier. The 'else' block has the answer to the question. public void start() { //removed code for clarity if (isMultioutput()) { //We just discussed the code in the following 'if' block } else { Outcome out = getWriterMapper().getOutcome("", this); if (out.getWriter() != null) { Map m = new HashMap(contextObjects); Collection filtered = CollectionUtils.select(metadata, new Predicate() { public boolean evaluate(Object o) { return shouldGenerate(o); } }); // in case of say velocity template $metadata would be a collection of // JavaClass in this case m.put("metadata", filtered); populateContextMap(m); templateEngine.generate(out.getWriter(), m, getEncoding(), getClass()); if (validate && outputValidator != null) { outputValidator.validate(out.getURL()); } out.getWriter().close(); } } This piece of code that uses Commons Collection's CollectionUtils class caught my attention (may be because I have never used Commons Collections before). Collection filtered = CollectionUtils.select (metadata, new Predicate () { public boolean evaluate (Object o) { return shouldGenerate(o); } }); I had looked at Groovy (codehaus seems to be a gold mine of frameworks (or languages in this case)!) sometime back and this seems to be the Java equivalent of Groovy closures at work. The predicate is evaluated for every item in the collection (metadata). If the predicate returns a value 'true' for that item, the resulting filtered collection would have that item else it is rejected. Note that, as in the earlier case, the custom plugin gets to decide if the source corresponding to the metadata object needs to be processed. In Python, the filter () construct would have done the job for me. The EjbJarXmlPlugin.jelly uses it as follows Note that this is not the complete template file. Certain parts have been ommited to show how a jelly template file typically looks like. <j:jelly xmlns:j="jelly:core"> <ejb-jar> <enterprise-beans> <!—Note that $metadata is a collection unlike in the earlier case. Its being read into the class variable in a loop - -> <j:forEach var="class" items="${metadata}"> <j:if test="${plugin.shouldGenerate(class)}"> <entity> <ejb-name>${plugin.ejbUtils.getEjbName(class)}</ejb-name> <ejb-class>${class.fullyQualifiedName}</ejb-class> </entity> </j:if> </j:forEach> </enterprise-beans> </ejb-jar> </j:jelly> There were certain things that did'nt necessarily make sense to me. Generama MetaDataProvider interface does'nt look generic enough. It has methods like String getOriginalFileName(Object metadata); ConfigurableDocletTagFactory getDocletTagFactory (); The issue here is that ConfigurableDocletTagFactory extends DocletTagFactory - a QDox interface. Similary org.generama.Plugin class that all plugins extend has an abstract method public abstract Collection getMetaData(); I think the responsiblity for providing access to the metadata has been wrongly placed on the Plugin Class?. This seems a little contradictory to the claim that Generama does'nt care where the metadata comes from. It probably makes more sense on the MetaDataProvider interface?. Hopefully a little bit of refactoring/'moving around of methods' should fix these issues. In its current state however, there are no issues since we are more likely to use xdoclet to generate code from java sources and QDox is apparently very efficient and fast when compared to xjavadoc - tool used by XDoclet1.x to gather metadata. How to validate the generated output?This is a new addition to Generama in the latest release - ability to conditionally validate the generated output. The plugin can register a org.generama.OutputValidator with the abstract plugin class and get it to validate the generated output. Generama ships an xml validator by default - org.generama.defaults.XMLOutputValidator. It can be used to validate generated XML files against a schema/dtd. For eg , while generating struts-config.xml from other struts related artifacts. Most of the plugins that generate XML files might have XMLOutputValidator registered by default. Note that validation is also switched on by default. If you don't want to validate the generated XML files, it can be done by setting attribute 'outputValidator' to false when invoking the plugin. <component class="org.xdoclet.plugin.hibernate.HibernateMappingPlugin" outputValidator="false"/>
|
||||
|
Copyright 2003-2006 - The Codehaus. All rights reserved unless otherwise noted.
Powered by Atlassian Confluence
|
|||||