Pages

Saturday, October 11, 2008

AS Model generator : XDoclet2 plugin to generate the Flex model AS file for the respective java server side value objects

My previous post gave an introduction to this plugin for xdoclet2 by writing a tutorial on xdoclet2 and velocity with this plugin as an example. Now, I will explain what this plugin can do.

First download the latest plugin(as3-plugin.jar) from dist folder at github project:

https://github.com/shreyaspurohit/AS3Generator

The supported annotations are:

1. as3.class - A class level tag.

Attributes:
1.a. name - The name of the generated AS class. Should be same as the java class name. Required.
1.b. generate - On false, does not generate the AS file for the java class. Default: true.
1.c. generate-bindable-metadata - Generates [Bindable] at class level in AS. Default value: true.
1.d. generate-remote-class-metadata - Generates [RemoteClass(alias="..")] on true. Default value: false.

2. as3.field - A Field level tag.

Attributes:

2.a. type - The fully qualified flex type to be generated in the AS file. Required.
2.b. import - Imports the flex type defined above. Default: false.
2.c. generate-bindable-field-metadata - Generate field level [Bindable] in the AS file. Default: false.
2.d. generate - Controls generation of the field in as files. Default: true.

The Java-Flex mapping supported are:

1. int - Number
2. double - Number
3. long - Number
4. java.lang.Short - Number
5. java.lang.Byte - Number
6. java.lang.Integer - Number
7. java.lang.Double - Number
8. java.lang.Long - Number
9. java.lang.Float - Number
10. java.lang.String - String
11. java.lang.Character - String
12. java.util.Collection - mx.collection.ArrayCollection
13. java.util.Map - Object
14. java.util.Dictionary - Object

For any other type define the as3.field with the flex type, and if the import is necessary. If that does not solve the problem, define it as Object, then cast it in the flex code when necessary. If this too does not solve the problem contact me on my blog. Will add the feature and release the next version.

Usage:

Write a ant build script and invoke xdoclet with target:

<target name="generate">
        <xdoclet>
        <fileset dir="${basedir}/src">
             <include name="**/*.java"/>          
         </fileset>
        <component classname="com.ssb.plugin.as3.As3Plugin"
                   destdir="${basedir}/src"/>
        </xdoclet>
</target>

Please look at my previous post for the sample model class which uses all these annotations.
Download or browse the latest sample from the github sample folder and see the build file for details.

XDoclet2 Custom Plugin ( Actionscript3 (AS3) model generator from java value objects ) and Velocity Tutorial

I work on flex and it was necessary for me to generate the flex side model classes in sync with the java side value objects. I Googled but was not able to find one that is as simple as the one written by Joe Berkovitz of Allurent. It was pretty old code and lacked configuration options for generating AS classes. I decided to write my own plugin for xdoclet2 that can be used to generate the flex code. And, here given below is a tutorial for learning xdoclet2. Since, the template engine it uses is velocity, this post also serves as a tutorial for velocity. Finally, I provide you with a complete working edition of the xdoclet2 plugin, and a sample to use the same.

Please look at the end of this tutorial to find links to the source, plugin jar and sample files.

System Configuration
1. xdoclet2 v1.0.4
2. velocity 1.5
3. JDK 5 update 15
4. ant 1.7.1

XDoclet2

XDoclet2 allows you to read the annotations on the java source files, and generate either XML or Java or any other file. It uses velocity template engine for any file type generation, and Jelly template engine for XML generation.

Step 1:

Download the xdoclet2 plugin distribution from http://sourceforge.net/projects/xdoclet-plugins/
Extract the zip or the tar.gz to an comfortable location.

Step 2:

Write the sample java class with the annotations that needs to be supported by the plugin. In my case I wrote Model.java. A sample snippet is given below:

package com.ssb.sample;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
*
* @author Shreyas
* @as3.class name="Model" generate="true" generate-bindable-metadata="true" generate-remote-class-metadata="true"
*/
public class Model {
    private int i;
    private double d;
    private long l;
    /**
     * @as3.field type="Number" import="false" generate-bindable-field-metadata="true"
     */
    private BigDecimal bdecimal;
    /**
     * @as3.field type="mx.flex.BigInt" import="true"
     */
    private BigInteger binteger;

    /**
     * @as3.field type="mx.flex.BigInt" generate="false"
     */
    private transient BigInteger doNotGeneratebinteger;
    .
    .
    .
Step 3:

Define Tag validators. This is helpful for validating if the annotations provided are as anticipated. Tag validators extends DocletTag as shown below. The interface itself is annotated using the qtags.

package com.ssb.plugin.as3.qtags;
import com.thoughtworks.qdox.model.DocletTag;
/**
*
* @qtags.location class
* @qtags.once
*
*/
public interface As3ClassTag extends DocletTag{
    /**
     * @qtags.required
     */
    String getName_();
    /**
     * @qtags.default true
     */
    String isGenerate();
    /**
     * @qtags.default true
     */
    String isGenerateBindableMetadata();
    /**
     * @qtags.default false
     */
    String isGenerateRemoteClassMetadata();
}

There are some points to be noted:
a. There is a one to one matching between the name of the interface - As3ClassTag -  and the annotation on the sample class -as3.class-. The First letter is capitalized, the '.' is removed, the next letter is also capitalized, a 'Tag' is appended.
b. The methods in the interface uses the standard java bean convention. For each attribute in the tag, 'get' or 'is' is prefixed to the attribute name with the first letter capitalized.
c. 'name' is a special attribute, a '_' should be used at the end of the getter to avoid internal naming collision.
d. The attribute when contains '-', the methods in the interface nelects it and follows the first letter capitalization rule.
e. Do not use camel casing in the sample annotation. Use only small letters or hyphens to separate for ease of work.

Definitions of tags:
a. qtags.location Whether the tag applies to class level or field level or method level, repectively, we have class, field, method as its values.
b. qtags.once The tag can be used only once in teh source file.
c. qtags.required The field is required.
d. qtags.default The default value of the field.

Step 4:

Generate the tag validator implementation and tag library. The tag library will be used by our plugin. A ant script is used to generate these items. The target to generate is given below. Please look at source code to get the entire build script.

<target name="gen.qtags.impl">
      <property name="xdoclet.qtags.namespace" value="as3"/>

      <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="com.ssb.plugin.${xdoclet.qtags.namespace}.qtags"
           />
      </xdoclet>
</target>

Step 5:

Write the xdoclet2 plugin. As3Plugin extends QDoxPlugin.

package com.ssb.plugin.as3;
import java.util.*;
import org.apache.log4j.Logger;
import org.generama.*
import com.ssb.plugin.as3.qtags.TagLibrary;
import com.thoughtworks.qdox.model.*;

public class As3Plugin extends QDoxPlugin{
    private Map<String, String> typeMap = new HashMap<String, String>();
    public As3Plugin(VelocityTemplateEngine templateEngine,
            QDoxCapableMetadataProvider metadataProvider,
            WriterMapper writerMapper) {
        //Call the superclass constructor.
        super(templateEngine, metadataProvider, writerMapper);
        ..
        ..
        ..

In the above snippet, the constructor takes VelocityTemplateEngine as argument. Do not bother how it gets that. The xdoclet is responsible for injecting it to the constructor. First, call the super class constructor.

        //Replace .java with .as extensions
        setFileregex(".java");
        setFilereplace(".as");
        //Set Multiple file output to true.
        setMultioutput(true);
        //Instantiate the generated tag library.
        new TagLibrary(metadataProvider);
        //Initialize the the type map
        initTypeMap();
The comments are pretty clear for the above code snippet. This ends the constructor.

The initTypeMap method is shown below.

    /**
     * Initializes the type map.
     */
    protected void initTypeMap() {
        typeMap.put("int", "Number");
        typeMap.put("double", "Number");
        typeMap.put("long", "Number");
        typeMap.put("java.lang.Short", "Number");
        typeMap.put("java.lang.Byte", "Number");
        ..
        ..
        ..
    }

    /**
     * Over ridden method, determines whether the given java class should be converted to as3 or not.
     *
     * @param metadata A java Class.
     * @return
     */
     public boolean shouldGenerate(Object metadata) {
            JavaClass javaClass = (JavaClass) metadata;
            boolean ignore = "false".equalsIgnoreCase(javaClass.getNamedParameter("as3.class","generate"));
            if (!ignore)
              return true;
            else
              return false;
     }
The above method decides whether AS files should be generated or not for the annotated java file. This is called by xdoclet and the Object argument is a JavaClass type. The getNamedParameter takes the annotation name and the attribute whose value has to be retrieved. If, it is false then do not generate the AS file.

    protected void populateContextMap(Map map) {
        super.populateContextMap(map);
        map.put("tagUtil", new TagUtil());
    }

The above code initializes the context map by adding tagUtil, so that it can be accessed by velocity template.

Now for each of the annotation and its attribute write a public method to get its value. This is present in TagUtil.java. Remember, it is public. Protected will not work, as this will be later used by the Velocity. Here below are sample for two of them:

    /**
     * Gets the As3 Class name from the annotation as3.class, attribute 'name'
     *
     * @param metadata The java class.
     * @return
     */
    public String getAs3Name(Object metadata){
        JavaClass javaClass = (JavaClass) metadata;
        return javaClass.getNamedParameter("as3.class","name");
    }
    /**
     * Gets the Field type from the annotation as3.field, attribute 'type'
     *
     * @param metadata The java Field.
     * @return
     */
    public String getTypeName(Object metadata){
        JavaField javaField = (JavaField)metadata;
        return getTagValue("as3.field", "type", javaField);
    }   
    private String getTagValue(String tagName,String tagAttribute, JavaField field){       
        return field.getNamedParameter(tagName, tagAttribute);
    }
This is it!! Really, this is all it takes to write a XDoclet related code for the plugin. Now, we have to integrate it with velocity to generate the output AS files.

Step 6:

Write the Velocity template file for Code generation.

Velocity

Step 1: Create a As3Plugin.vm file at com.ssb.plugin.as3 package, same package as the plugin. The file name must identical to the plugin name.

Step 2: Write the velocity code in the vm file. It is shown below as snippets.

// ${dontedit}
#set( $class = $metadata )
#set( $truevalue = "true")

The ${} are variables that can hold values in the VTL(Velocity template language). #set is used to assign values. In working with xdoclet, the $metadata is injected from xdoclet and holds the class name. $truevalue is assigned string 'true';

package $plugin.getDestinationPackage($class);
{

#foreach($field in $class.getFields())
#set($import = $plugin.shouldImport($field))
  #if($import != "false")
    import $import;
  #end
#end
#if($tagUtil.isGenerateBindableMetadata($class) == $truevalue)
    [Bindable]
#end
..
..

Other variables that are available to the velocity are $plugin and $tagUtil. $plugin holds the instance of the As3Plugin class. Velocity can be used to invoke java methods through this variable, passing any arguments if required. The getDestinationPackage method is present in the base class of the As3Plugin. It gives the package of the java class. The package of the AS class will be same as this one.
#foreach is a directive to loop in velocity. Velocity supports #if#elseif#else#end directive also. Any method you see being invoked on $plugin is actually being invoked on As3Plugin java side object. So, shouldImport calls the As3Plugin classes shouldImport method. Similarly, isGenerateBindableMetadata calls the TagUtil classes isGenerateBindableMetadata method.

Velocity supports writing methods, by defining macro's etc, but that is not needed for writing this template file. That's all is velocity!! Its the most basic stuff in velocity that is being used here. Please refer velocity user manual for more detailed information( http://velocity.apache.org/engine/devel/user-guide.html ).

Every thing needed for our plugin is now in place. Just compile the classes, include all the resources and build the plugin jar. The build script is provided in the source.

To use the plugin, write a ant build script as given below:

<?xml version="1.0" encoding="ISO-8859-1"?>
<project name="Test AS3 Plugin Jar" default="main">

    <property file="build.properties"/>
    <path id="xdoclet.task.classpath">
        <!-- xdoclet2 runtime dependencies -->

          <fileset dir="${xdoclet.plugin.install.dir}/lib">
            <include name="**/*.jar"/>
          </fileset>
        <pathelement location="${basedir}/lib/as3-plugin.jar"/>

    </path>

    <!-- Define xdoclet task -->
     <taskdef
        name="xdoclet" classname="org.xdoclet.ant.XDocletTask"
         classpathref="xdoclet.task.classpath"
     />
    <target name="main" depends="generate"/>
    <target name="generate">
        <xdoclet>
        <fileset dir="${basedir}/src">
             <include name="**/*.java"/>          
         </fileset>
        <component classname="com.ssb.plugin.as3.As3Plugin"
                   destdir="${basedir}/src"/>
        </xdoclet>
     </target>
</project>

build.properties contain:
xdoclet.plugin.install.dir = E:/DevToolsInstalled/xdoclet-plugins-dist-1.0.4

This completes the tutorial for writing xdoclet2 plugin using velocity template engine. This generates non java files, .as, as output.

Resources:

Go to: https://github.com/shreyaspurohit/AS3Generator

1. Download the latest plugin jar from dist folder (as3-plugin.jar) to use the lib.

2. Download the latest source of the plugin by cloning git (or download zip). Edit the build.properties to provide the xdoclet2 installation directory.
Run ant on build.xml to generate the as3-plugin.jar in the base directory.

3. Download the latest samples from sample folder by cloning git (or download zip). Edit the build.properties to provide the xdoclet2 installation directory.
Run ant on build.xml to generate Model.as, Model2.as at com.ssb.sample.

4. Download the latest java doc from folder api on git by cloning or downloading zip.

Please read my next post to understand the capabilities of the plugin.

Wednesday, October 01, 2008

Step by step CXF Webservice Tutorial

We had an application running, and a webservice had to be exposed. With some experience in Axis2, I decided to learn some thing new, CXF service. Well Initially, I gave up on CXF service.(Will give the details in some time). So, thought let me use the Axis2 service. But, the only way I knew to use it was deploying the axis as webapp,and then writing and deploying the service as aar. So, a search to find integrating Axis2 with an existing webapp begins! And, with shame, I can tell, I was not able to find any where a clue about doing it. I could have experimented by including there axis distribution lib, and its web.xml content in my applications, but decided to learn some thing newer. And, hence the CXF.

I was developing a flex app on the blazeds turnkey server that is distributed by adobe. And, the webservice had to be integrated with this existing application. Since, CXF was the one I wanted to learn, I tried using it(as given below in tutorial), but always ended up either with one or other exceptions as given below.

1. On startup  of server this exception used to occur when all the jars of CXF are put in the WEB-INF/lib folder.

SEVERE: Context initialization failed
org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected exception parsing XML document from class pat
h resource [com/ssb/service/data.xml]; nested exception is java.lang.IllegalArgumentException: Class [org.spring
framework.scripting.config.LangNamespaceHandler] does not implement the NamespaceHandler interface
Caused by: java.lang.IllegalArgumentException: Class [org.springframework.scripting.config.LangNamespaceHandler] does no
t implement the NamespaceHandler interface at org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver.initHandlerMappings(DefaultNamespaceHan


2. When the spring-XXXX.jars were removed(Google helped me to point out this solution!), and the webapp was started, no problem at all. The server was up and running. But, on accessing the service as:

http://localhost:8400/cxftry/services

This exception was that I had:

java.lang.NoSuchMethodError: org.springframework.context.ConfigurableApplicationContext.addApplicationListener(Lorg/springframework/context/ApplicationListener;)V
org.apache.cxf.transport.servlet.CXFServlet.loadSpringBus(CXFServlet.java:104)
org.apache.cxf.transport.servlet.CXFServlet.loadBus(CXFServlet.java:70)
org.apache.cxf.transport.servlet.AbstractCXFServlet.init(AbstractCXFServlet.java:90)

With no information on web about any of these with CXF, I guessed the problem is the appserver, turnkey, that I was using. Though, it uses tomcat 6, not sure why it doesnt work. I downloaded a tomcat 6 server, and put the application on it and it worked like a charm. :o)

System Configurations:

1.CXF 2.1.2

2.JDK 1.5

3.TOMCAT 6

Here is the tutorial, to use CXF to expose as a webservice with Java-First methodology.

1. Download the CXF from http://cxf.apache.org/download.html
2. Extract to a directory(here, I will call it as CXF_HOME)
3. Copy paste all the lib(not recomended, go through WHICH_JARS present in CXF_HOME/lib to decide on the jars you need for your application) to WEB-INF/lib directory.
4. Write your Service Endpoint Interface (SEI), nothing but a java interface that will be exposed as a webservice.

package com.ssb.service;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.xml.ws.WebFault;

import com.ssb.exception.SomeException;
import com.ssb.model.Data;

@WebService
public interface IDataService {
    @WebMethod(operationName="getData")
    public Data  getData(@WebParam(name="id")String id) throws SomeException;
}

5. Annotate your Exception with @WebFault if any.

package com.ssb.exception;

import javax.xml.ws.WebFault;

@WebFault(name="exception")
public class SomeException extends Exception {

    public String contactInfo = "Sacrosanct Blood.";
    private static final long serialVersionUID = 1L;

    private SomeException() {
    }

    public SomeException(String message, Throwable cause) {
        super(message, cause);
    }

    public SomeException(String message) {
        super(message);
    }
}

6. Annotate your Service Implementation as a webservice.

package com.ssb.service;

import javax.jws.WebService;

import com.ssb.exception.SomeException;
import com.ssb.model.Data;

/**
* @author shreyas.purohit
*
*/
@WebService(endpointInterface="com.ssb.service.IDataService", serviceName="dataService")
public class DataServiceImpl implements IDataService {

    /**
     *
     */
    public DataServiceImpl() {
    }

    public Data getData(String id) throws SomeException{
        Data data = new Data();
        return data;
    }

}

7. Configure the data.xml for the webservice(com/ssb/service/data.xml).

<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:jaxws="http://cxf.apache.org/jaxws"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd">

  <import resource="classpath:META-INF/cxf/cxf.xml" />
  <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
  <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
  <jaxws:endpoint id="data"
                  implementor="com.ssb.service.DataServiceImpl"
                  address="/dataService"/>
</beans>

8. Configure web.xml for CXF to work.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>

<display-name>CXF</display-name>
    <description>CXF Application</description>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:com/ssb/service/data.xml</param-value>
      </context-param>
      <listener>
        <listener-class>
          org.springframework.web.context.ContextLoaderListener
        </listener-class>
      </listener>
      <servlet>
        <servlet-name>CXFServlet</servlet-name>
        <servlet-class>
            org.apache.cxf.transport.servlet.CXFServlet
        </servlet-class>
      </servlet>
      <servlet-mapping>
        <servlet-name>CXFServlet</servlet-name>
        <url-pattern>/services/*</url-pattern>
      </servlet-mapping>
    <welcome-file-list>
            <welcome-file>index.html</welcome-file>
            <welcome-file>index.htm</welcome-file>
    </welcome-file-list>
</web-app>

9. Start the server, and access http://localhost:8080/cxftry/services
You should be able to see the services. On clicking on it, the wsdl can be seen.

The annotations used in the above code is very simple and self explaining.

1. @WebService : Indicates its a webservice.

Attributes used:

endpointInterface=Specifies the full name of the SEI that the implementation class implements.
serviceName=Specifies the name of the published service.

2. @WebMethod    : Indicates a webservice method.

Attributes used:

operationName=Specifies the operation name, i.e the webservice method name.

3. @WebParam    : Used to give the parameters in the operations a meaningfull name in the published WSDL.

Attributes used:

name= Name of the argument to be displayed on the WSDL for the operation.

4. @WebFault    : Defines an exception that the operation/web service method can throw.

Attributes used:

name= The name to be used in the WSDL.

This completes a quick tutorial for publishing a Java-First webservice.