Sunday 27 May 2007

On using the echo2 googlemaps component

There is a echo2 component for google maps
here. And it works really good so far.

The author of this component says that the echo2 code has to be patched such that the google map api can be loaded at page load time which is needed for google maps (at least with echo2 version 2.1.0beta5).

I managed to get the component running with echo2 2.1.0rc2 without patching:

The starting point for an echo2 application is a servlet which extends nextapp.echo2.webrender.WebRenderServlet. The normal behaviour of this servlet is to register some services. One of these services is the nextapp.echo2.webcontainer.WindowHtmlService which produces the output in its service method. The trick is now to replace this class with your own implementation.

I made it this way:

The constructor of my HelloWorldServlet which extends nextapp.echo2.webrender.WebRenderServlet and which is the starting point of my echo2 application is the following:

public HelloWorldServlet()
{
super();

final String METHOD = "HelloWorldServlet: ";

ServiceRegistry serviceRegistry = WebRenderServlet.getServiceRegistry();
serviceRegistry.remove(WindowHtmlService.INSTANCE);
serviceRegistry.remove(NewInstanceService.INSTANCE);
serviceRegistry.add(MyNewInstanceService.INSTANCE);
serviceRegistry.add(MyWindowHtmlService.INSTANCE);

log.debug(METHOD+"successful");
}

This removes the old service implementations and replaces it with my own implementations:

package de.banapple.echo2test;

import java.io.IOException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import nextapp.echo2.webcontainer.ContainerInstance;
import nextapp.echo2.webrender.Connection;
import nextapp.echo2.webrender.Service;

public class MyNewInstanceService
implements Service
{
private static Log log = LogFactory.getLog(MyNewInstanceService.class);

public String getId()
{
return "Echo.NewInstance";
}

public int getVersion()
{
return -1;
}

public void service(Connection conn)
throws IOException
{
final String METHOD = "service: ";
log.debug(METHOD+"conn="+conn);

ContainerInstance.newInstance(conn);
MyWindowHtmlService.INSTANCE.service(conn);
}

public static final MyNewInstanceService INSTANCE =
new MyNewInstanceService();

}



package de.banapple.echo2test;

import java.io.IOException;

import nextapp.echo2.webcontainer.ContainerInstance;
import nextapp.echo2.webcontainer.WindowHtmlService;
import nextapp.echo2.webrender.BaseHtmlDocument;
import nextapp.echo2.webrender.Connection;
import nextapp.echo2.webrender.ContentType;
import nextapp.echo2.webrender.output.CssStyle;
import nextapp.echo2.webrender.service.CoreServices;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Element;

public class MyWindowHtmlService
extends WindowHtmlService
{
private static Log log = LogFactory.getLog(MyWindowHtmlService.class);

public void service(Connection conn)
throws IOException
{
final String METHOD = "service: ";
log.debug(METHOD+"conn="+conn);

ContainerInstance ci = (ContainerInstance)conn.getUserInstance();
conn.setContentType(ContentType.TEXT_HTML);
boolean debug = !"false".equals(conn.getServlet().getInitParameter("echo2.debug"));
BaseHtmlDocument baseDoc = new BaseHtmlDocument("c_root");
baseDoc.setGenarator("NextApp Echo v2.1.0.rc2");

/* add google maps api */
baseDoc.addJavaScriptInclude(
"js/googleload.js");

baseDoc.addJavaScriptInclude(ci.getServiceUri(CoreServices.CLIENT_ENGINE));
baseDoc.getBodyElement().setAttribute("onload", "EchoClientEngine.init('" + ci.getServletUri() + "', " + debug + ");");
Element bodyElement = baseDoc.getBodyElement();
CssStyle cssStyle = new CssStyle();
cssStyle.setAttribute("position", "absolute");
cssStyle.setAttribute("font-family", "verdana, arial, helvetica, sans-serif");
cssStyle.setAttribute("font-size", "10pt");
cssStyle.setAttribute("height", "100%");
cssStyle.setAttribute("width", "100%");
cssStyle.setAttribute("padding", "0px");
cssStyle.setAttribute("margin", "0px");
cssStyle.setAttribute("overflow", "hidden");
bodyElement.setAttribute("style", cssStyle.renderInline());
baseDoc.render(conn.getWriter());
}

public static final MyWindowHtmlService INSTANCE =
new MyWindowHtmlService();
}


The implementation of the service method of MyWindowHtmlService is copied from the original implementation and extended with the
baseDoc.addJavaScriptInclude("js/googleload.js"); which loads the google maps api with my key.

Ok, that is not really far away from patching the class, but still... It might work with forthcoming versions of echo2.

Wednesday 23 May 2007

Maven archetype for echo2 application

While testing echo2
I searched for a maven archetype which creates an initial echo2 application..,
and did not find one.

So I made one available at
http://maven-repository.banapple.de/de/banapple/maven/archetype/

If you are interested download the jar and install it in your local maven repository and then
create your echo2 app with

# mvn archetype:create \
-DarchetypeGroupId=de.banapple.maven.archetype \
-DarchetypeArtifactId=echo2 \
-DarchetypeVersion=1.0-SNAPSHOT \
-DgroupId=<your groupId here> \
-DartifactId=<your artifactId here>


Change to the newly created directy and test the application with

# mvn jetty:run


This starts a jetty server on port 8080 in which the application runs.

The archetype uses echo2 libraries which are deployed on
maven-repository.banapple.de, too.

Thursday 17 May 2007

Creating POST requests with curl for Axis2 REST service

Axis2 allows to create REST services as easily as SOAP services. By default each
SOAP service has a REST counterpart automatically available.

The responses for REST requests served by Axis2 are the same as SOAP responses but
are missing the enclosing SOAP envelope (this is because REST is handled inte
rnally like a SOAP request). Because of this the responses are currently missing
the REST feature to return links to other resources in the response.

But creating REST requests is somewhat easier than SOAP requests. This article
describes an example where a REST request is sent with curl.

Axis2 uses document-style services by default. The following excerpt of the
webservice WSDL which was used shows the XML schema for the data to be transmitted:


<xs:schema xmlns:ns="http://impl.service.banapple.de/xsd" attributeFormDefault="qualified" elementFormDefault="unqualified" targetNamespace="http://impl.serv ice.banapple.de/xsd">
<xs:element name="createContact">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="incomingData" nillable="true" type="ns10:IncomingData" />
<xs:element maxOccurs="unbounded" name="foreignKeys" nillable="true" type="ns10:IncomingForeignKey" />
<xs:element name="incomingCreator" nillable="true" type="ns10:IncomingCreator" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="createContactResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="return" nillable="true" type="xs:int" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
<xs:schema xmlns:ax242="http://bean.model.banapple.de/xsd" xmlns:ax283="http://lang.java/xsd" xmlns:ax244="http://util.java/xsd" attributeFormDefault="qualified" elementFormDefault="unqualified" targetNamespace="http://bean.model.banapple.de/xsd">
<xs:import namespace="http://util.java/xsd" />
<xs:import namespace="http://lang.java/xsd" />
<xs:element name="IncomingData" type="ns10:IncomingData" />
<xs:complexType name="IncomingData">
<xs:sequence>
<xs:element name="creationDate" type="xs:long" />
<xs:element name="dataTypeName" nillable="true" type="xs:string" />
<xs:element name="dataValue" nillable="true" type="xs:string" />
</xs:sequence>
</xs:complexType>
<xs:element name="IncomingForeignKey" type="ns10:IncomingForeignKey" />
<xs:complexType name="IncomingForeignKey">
<xs:sequence>
<xs:element name="foreignKeyTypeName" nillable="true" type="xs:string" />
<xs:element name="foreignKeyValue" nillable="true" type="xs:string" />
</xs:sequence>
</xs:complexType>
<xs:element name="IncomingCreator" type="ns10:IncomingCreator" />
<xs:complexType name="IncomingCreator">
<xs:sequence>
<xs:element name="name" nillable="true" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:schema>


Two namespaces are used:
http://impl.service.banapple.de/xsd for the element describing the
operation,
http://bean.model.banapple.de/xsd for the parameters of the operation.

The example payload for the request is the following:


<createContact
xmlns="http://impl.service.banapple.de/xsd"
xmlns:bean="http://bean.model.banapple.de/xsd">
<bean:incomingData>
<creationDate>0</creationDate>
<dataTypeName>CUSTOMER_ID</dataTypeName>
<dataValue>300042045</dataValue>
</bean:incomingData>
<foreignKeys>
<foreignKeyTypeName>UCID</foreignKeyTypeName>
<foreignKeyValue>1</foreignKeyValue>
</foreignKeys>
<incomingCreator>
<name>Foobar</name>
</incomingCreator>
</createContact>


The payload was stored in a file createcontact.xml and sent to the
REST service with curl with the command:


curl -H "Content-Type: text/xml; charset=UTF-8"
--data-binary @createcontact.xml
http://localhost:8080/contactdb/rest/DataReceiverService/createContact



It is important to sent the Content-Type header otherwise the REST
service will produce an error response with the not very helpful message

Required element null defined in the schema can not be found in the request

Thursday 10 May 2007

Adding Axis2 webservice programmatically at runtime

I just tried out Axis2 (in version 1.1.1).

But I wanted to embed the AxisEngine into my own web application instead of building an aar and deploying it into the axis2 default web application. One possible way would have been to use the same web application directory layout as used in the axis2 default web app and put a service archive into the WEB-INF/services directory or to package my web application war having the exploded service archive in the WEB-INF/services. Both solutions would have complicated the build process.

So I wanted to add services programmatically at runtime. Perhaps using some annotations on classes which mark them as webservices.

I found code like that:

service = AxisService.createService(
MyServiceImpl.class.getName(),
axisConfiguration,
RPCMessageReceiver.class);
service.setName("MyService");
axisConfiguration.addService(service);
axisConfiguration.startService(service.getName());


But the problem was to get the AxisConfiguration. The solution was simple to extend the AxisServlet which has a reference to the configuration. So MyAxisServlet overrode the init method in the following way...


public void init(ServletConfig config) throws ServletException
{
super.init(config);

AxisService service;
try {
service = AxisService.createService(
MyServiceImpl.class.getName(),
axisConfiguration,
RPCMessageReceiver.class);
service.setName("MyService");

axisConfiguration.addService(service);
axisConfiguration.startService(service.getName());
} catch (AxisFault e) {
e.printStackTrace();
}
}


...and substituted the default AxisServlet in the web.xml with MyAxisServlet ...and it worked.

At the moment I don't know if I made everything right but I had a webservice running.

The next step was then to change the init method to search all classes having an annotation @Webservice and add them in the same way as above. Name, scope and targetNamespace were set with annotation attributes.

So I have my classes annotated and added automatically to the AxisEngine. When I add another webservice class to my web app the only thing to do is to add the annotation. And I don't need a special build process to build the archive or package my war file.

Tuesday 1 May 2007

Versioning of database entities with Hibernate

At work I had the requirement to introduce a versioning mechanism for database entities in an already productive application which used Hibernate as persistence framework.

Versioning was meant in the following way: the state of an entity had to be reconstructable at any point of time and for each change the id of the changer and the time had to be stored. In short: who made what when?

The following ideas and techniques are not new and are not my own ideas. The implementation orients itself on suggestions from the book "Hibernate in Action" written by Gavin King and Christian Bauer, which I can recommend.

The database in use did not have triggers, therefore an implementation using database triggers was not possible.
Instead of that the mechanism was to be implemented with Hibernate means.

Since the application was already in production the changes had to be minor.
One possibility to achieve the goal would have been to introduce a superclass for all entities to be versioned. But this would have been a bigger change. The risk of errors would have been to great.

The implementation was done in the following way:
1. a new annotation Historicizable marks the entities to be versioned
2. two new database tables were introduced: EntityHistory and EntityHistoryData. The former includes class name and id of the changed entity, date of the change, the initiator of the change and the type of change (INSERT, UPDATE, DELETE). The latter contains information about each changed attribute of the entity. There is a one-to-many relationship between them.
3. an Interceptor was defined which collects data about changes and stores them.

The Interceptor
gets registered at the SessionFactory. It is therefore used in each Hibernate session. Our interceptor implements the methods onSave, onFlushDirty, onDelete, afterTransactionCompletion and postFlush. The first three just collect information about changes and create a list of EntityHistory beans. The interceptor checks whether the entity for which the interceptor was called is annotated with Historicizable. If not no data is collected.
afterTransactionCompletion checks whether the current transaction was rolled back. In this case the list of collected EntityHistory beans is cleared.
Method postFlush makes the list of EntityHistory beans persistent. This method has to use its own Hibernate Session but which can share the same connection.

The existing API method of the application did not have an argument which defines the initiator of a change. Instead of changing each of these methods the initiator data can be set as a ThreadLocal variable on the interceptor. So the versioning mechanism relies on the client of the application API to set the initiator properly before calling any API method which could possibly include versioning.

E.g. some API methods were published as webservices. The webservices itself were extended so that initiator data could be send as a SOAP header with the request.

This versioning mechanism only works for entities having only primitive or simple properties. Versioning of entities with references to other entities is not supported.