Grails Web Services

In this blog we’ll expose a Grails service as a SOAP service. According to good practice we’ll build the WSDL first and then write the implementation code.
First things first. Let’s create a new grails application and call it grails-soap

grails> create-app grails-soap

 

CXF

For SOAP support we’ll be using the Grails CXF plugin. Just add the following line in the plugin section of your BuildConfig.groovy file to enable the plugin. Documentation for the plugin can be found here.

compile ':cxf:1.1.1'

 

WSDL

We’ll start with building the WSDL for the service. The service will be called DepartmentService and will return some Department data for a given id. We’ll place the WSDL in the src/java folder.

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions name="DepartmentService"
	targetNamespace="http://devjournal/DepartmentService" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
	xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12s/"
	xmlns:tns="http://devjournal/DepartmentService">
	<wsdl:types>
		<xsd:schema xmlns="http://www.w3.org/2001/XMLSchema">
			<import namespace="http://devjournal/DepartmentService"
				schemaLocation="DepartmentService.xsd" />
		</xsd:schema>
	</wsdl:types>
	<wsdl:message name="DepartmentServiceRequestMessage">
		<wsdl:part name="in" element="tns:DepartmentServiceRequest" />
	</wsdl:message>
	<wsdl:message name="DepartmentServiceResponseMessage">
		<wsdl:part name="out" element="tns:DepartmentServiceResponse" />
	</wsdl:message>
	<wsdl:portType name="GetDepartmentById">
		<wsdl:operation name="getDepartmentById">
			<wsdl:input message="tns:DepartmentServiceRequestMessage" />
			<wsdl:output message="tns:DepartmentServiceResponseMessage" />
		</wsdl:operation>
	</wsdl:portType>
	<wsdl:binding name="GetDepartmentByIdSoap12" type="tns:GetDepartmentById">
		<soap12:binding transport="http://schemas.xmlsoap.org/soap/http"
			style="document" />
		<wsdl:operation name="getDepartmentById">
			<soap12:operation
				soapAction="http://whitehorses.nl/Whitebook_SOA_UT/DepartmentService/GetDepartmentById"
				style="document" />
			<wsdl:input>
				<soap12:body use="literal" parts="in" />
			</wsdl:input>
			<wsdl:output>
				<soap12:body use="literal" parts="out" />
			</wsdl:output>
		</wsdl:operation>
	</wsdl:binding>
	<wsdl:service name="DepartmentService">
		<wsdl:port binding="tns:GetDepartmentByIdSoap12" name="GetDepartmentByIdSoap12">
			<soap12:address location="http://www.example.org/" />
		</wsdl:port>
	</wsdl:service>
</wsdl:definitions>

The underlying xml schema file contains the response element. This file will also be placed in the src/java folder.

<?xml version="1.0" encoding="UTF-8"?>
<schema attributeFormDefault="unqualified" elementFormDefault="qualified"
        targetNamespace="http://devjournal/DepartmentService"
        xmlns:tns="http://devjournal/DepartmentService"
        xmlns="http://www.w3.org/2001/XMLSchema">
 <element name="DepartmentServiceRequest">
  <complexType>
   <sequence>
    <element name="Id" type="int"/>
   </sequence>
  </complexType>
 </element>
 <element name="DepartmentServiceResponse">
  <complexType>
   <sequence>
    <element name="Department" type="tns:departmentType" maxOccurs="1"
             minOccurs="0"/>
   </sequence>
  </complexType>
 </element>
 <complexType name="departmentType">
  <sequence>
   <element name="Id" type="int"/>
   <element name="Name" type="string"/>
   <element name="Location" type="string"/>
  </sequence>
 </complexType>
</schema>

 

wsdl2java

Now that we have the WSDL it’s time to generate the Java stubs. Although the CXF plug-in comes with a wsdl-to-java command it kept giving me a “GroovyCastException” when I tried to run it. To workaround this problem, I downloaded the latest apache-cxf distribution here (2.7.11 at the time of this writing) and used its wsdl2java command to generate the stubs.

$ $CXF_HOME/apache-cxf-2.7.11/bin/wsdl2java DepartmentService.wsdl

After running this command from within the /src/java directory, the following stub classes are added to your grails application:

wsdl2java classes

wsdl2java classes

 

service

Now everything is in place, we can start implementing the service. First create a new grails service component called DepartmentService

grails> create-service department

We’ll have this service implement the generated @WebService interface GetDepartmentById and override the getDepartmentById method (corresponding with the SOAP operation of the same name) For simplicity sake, we’ll just hardcode a response:

package grails.soap

import javax.jws.WebService

import org.grails.cxf.utils.EndpointType

import devjournal.departmentservice.DepartmentServiceRequest
import devjournal.departmentservice.DepartmentServiceResponse
import devjournal.departmentservice.DepartmentType
import devjournal.departmentservice.GetDepartmentById


@WebService(targetNamespace = "http://devjournal/DepartmentService", name = "GetDepartmentById", serviceName = 'DepartmentService',
portName = 'GetDepartmentById')
class DepartmentService implements GetDepartmentById {

	static expose = EndpointType.JAX_WS_WSDL
	static soap12 = true
	static address = 'DepartmentService'
	
	def serviceMethod() {
	}

	@Override
	public DepartmentServiceResponse getDepartmentById(
			DepartmentServiceRequest departmentServiceRequest) {

		DepartmentType department = new DepartmentType(id: departmentServiceRequest.id, location:'Amsterdam', name: 'Sales')
		return new DepartmentServiceResponse(department:department)
	}
}

As you can see in the code above, we’ve implemented the GetDepartmentById interface and added the @WebService annotation to the service. With the serviceName and portName attributes we can influence the WSDL that’s eventually being served.
The cxf-plugin allows further configuration by means of static properties. Setting the soap12 property to “true” makes the DepartmentService adhere to the SOAP 1.2 specification. With the address property we can adjust the endpoint of the service. By default it would be the default address of the grails service, i.e. “/services/department”. In the above example the endpoint will be “/services/DepartmentService”. Refer to the cxf-plugin documentation for an overview of available properties.
 

Testing the web service

Now let’s fire up the application and give it a spin

grails> run-app

The WSDL should be available at http://localhost:8080/services/DepartmentService?WSDL and can be tested with SoapUI. The screenshot below shows the result.

DepartmentService Test

DepartmentService Test


 

Conclusion

In this blog I’ve shown how easy it is to expose a grails service via SOAP. Although the service had been turned into a SOAP service it’s still available as a normal grail service to the rest of the application, so there’s no violation of the DRY principle and code can be reused.

Grails in the Cloud Foundry

In this blog I’ll show you how easy it is to get a small Grails app up and running on Cloud Foundry making use of a mysql service provided by Cloud Foundry. We’ll be using the Groovy & Grails Toolsuite (ggts) for this, but I’ll also show you the grails commands you can use to accomplish each individual step.

Setup

  • At the time of this blogpost I’m using ggts version 3.1.0.RELEASE based on Eclipse Juno 4.2.1, which can be downloaded right here;
  • ggts comes with a full Grails installation. To make use of command line grails commands outside of the IDE, set the following environment variables. If you’re an Ubuntu user like me, just add these two lines to your ~/.bashrc file:
    • export GRAILS_HOME=<ggts_home>/grails-2.1.1;
    • export PATH=$PATH:$GRAILS_HOME/bin;
  • Apply for a Cloud Foundry account right here. You’ll receive an email of approval which will contain your  password;
  • Enable Cloud Foundry Integration in ggts. Click the Extensions link on the Dashboard, scroll down to Cloud Foundry Integration for Eclipse and install it.
Screenshot from 2013-02-17 15:40:52

Cloud Foundry Integration for Eclipse

Building a simple Grails app

We’re gonna develop a very simple Grails app. Just enough for demonstration purposes. It’ll be an app that enables you to create notes and store them in a database. We’ll call it one-note.

Grails Project

Choose File > New > Grails Project and supply the project-name. Alternatively you could use the command line and type:

grails create-app one-note

There’s also a command line interface available in ggts itself (Navigate > Open Grails Command Prompt). All commands are available here (just ommit the grails prefix), except for the creation of an application.

Screenshot from 2013-02-24 16:54:11

Grails Command Prompt

Domain Class

Choose File > New > Domain Class and name it Note. The Note.groovy domain class will be created for you. You can also create the class via this command line:

grails create-domain-class note

Add two simple fields to the domain class, so it looks like this:

package one.note

class Note {

    String content
    Date dateCreated

    static constraints = {
    }
}

The dateCreated is a standard field which will be supplied for by the Grails framework, the content field will contain the actual note.

Controller

Choose File > New > Controller and name it NoteController. The NoteController.groovy controller will be created for you. The equivalent command would be

grails create-controller note

Add scaffolding and reroute the index action to the list action. Your controller should now look like this

package one.note

class NoteController {

    def scaffold = true

    def index() {
        redirect(action:list)
    }
}

Testdrive the application

That’s basically all we need to run the application locally and make use of an in-memory hsqldb. Select Run > Run As > Grails Command (run-app), and point your browser to http://localhost:8080/one-note and you’re good to go. Select the NoteController in the Overview page, create some notes and your application should resemble the picture below.

Screenshot from 2013-02-24 18:05:49

one-note Grails application

Alternatively you could achieve the same via the command line:

grails run-app

Publish to Cloud Foundry

Although not entirely necessary, it’s a good practice to change your production configuration in the Datasource.groovy file. Add the following lines

environments {
    production {
        dataSource {
            dbCreate = &quot;update&quot;
            driverClassName = &quot;com.mysql.jdbc.Driver&quot;
        }
    }
}

Furthermore edit the BuildConfig.groovy and add the following dependency:

dependencies {
runtime &quot;mysql:mysql-connector-java:5.1.23&quot;
}

Cloud Foundry Integration style

Add a new server (File > New > Other > Server) and select Cloud Foundry as the type.

Screenshot from 2013-02-24 18:33:21

New Server > Define a New Server

Fill in your Cloud Foundry account information and press Validate Account to make sure you entered everything correctly.

Screenshot from 2013-02-24 18:33:35

New Server > Cloud Foundry Account

Now go to the Applications tab and Add the one-note application from Available to Configured and click Finish.

Change Runtime to Java 7, click Next >

Screenshot from 2013-02-24 18:38:11

Application > Application Details

Note the Deployed URL and click Next >

Screenshot from 2013-02-24 18:39:22

Application > Launch deployment

Click Add Service to add a mysql service, give it a name (I choose one-note) and select MySQL database as the type and click Finish.

Screenshot from 2013-02-24 18:40:33

Add Service > Service Configuration

Make sure, the new mysql service is selected and click Finish.

Screenshot from 2013-02-24 19:14:44

Application > Services selection

Now the application will get started and you’ll get a nice overview of your application running in Cloud Foundry.

Screenshot from 2013-02-24 18:49:41

Application overview

Also take note of the Remote Systems window in ggts. Here you can for one take a look at all the log files in your Cloud Foundry environment.

Screenshot from 2013-02-24 13:17:11

Remote Systems

That’s it. You can now test your application on the following url: http://one-note.cloudfoundry.com.

Screenshot from 2013-02-24 19:00:21

one-note in the cloud

Command line style

To enable Cloud Foundry commands via the command line you have to add a plugin to the application:

grails install-plugin cloud-foundry

Next add the following two lines to the ~/.grails/settings.groovy file:
grails.plugin.cloudfoundry.username = “yourusername”
grails.plugin.cloudfoundry.password = “yourpassword”

Now you can push the one-note application to Cloud Foundry:

grails prod cf-push

Accept the default url and  choose y at “Would you like to create and bind a mysql service” and n at the same question for a postgresql service.

While building the war for deployment, you might see and Error about a missing migrations directory. This is caused by the database-migration plugin. It’s harmless, but if you want to get rid of it, just add the migrations directory in the grails-app subdirectory of the application.

That’s it. You can test the application via the same url we used before when we used Cloud Foundry Integration: http://one-note.cloudfoundry.com.

There are lots of command available in the plugin. There are for example commands for restarting and deleting a deployed app or service. You can display the list of available commands with:

grails cf-help

Summary

In this blogpost we’ve built a simple Grails application and pushed it to Cloud Foundry, making use of a mysql database service. We’ve shown how to do it via the command line as well as via the Groovy/Grails Tool Suite with the Cloud Foundry Integration plugin. Whichever way you prefer, both ways are very powerful very easy to use. I myself often use a hybrid approach. For the creation of components and pushing the application to the cloud, I use the command line approach . And for managing the deployed applications, I make use of the nice overview interface that the Cloud Foundry Integration Plugin provides.

References

Developing a contract-first JAX-WS webservice

In this blog i’ll develop a simple webservice using JAX-WS. I’ll first start with the contract (wsdl and xsd’s). The contract will be used for generating the necessary JAXB artifacts. Getting the webservice up and running will be a piece of cake after that all thanks to Maven and JAX-WS.
First things first, let’s create a new Maven project. I’m using Netbeans as IDE for this.

New Maven Web Application

First Create a new Maven Web Application

and call it hello_person for example

The Contract

The webservice will accept a Person graph with a first and last name and will return a concatenated “Hello first name last name!”. Not very original but good enough for the example.
First we’ll define the xsd called helloPersonService.xsd for the request and response. The request will contain a Person object with a first and last name, the response will contain a Greetings string. We’ll store it in src/main/resources/xsd.

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            elementFormDefault="qualified"
            xmlns="http://example.nl/hellopersonservice/1.0"
            targetNamespace="http://example.nl/hellopersonservice/1.0">
    <xsd:element name="HelloPersonServiceRequest" type="HelloPersonServiceRequestType"/>
    <xsd:element name="HelloPersonServiceResponse" type="HelloPersonServiceResponseType"/>

    <xsd:complexType name="HelloPersonServiceRequestType">
        <xsd:element name="Person" type="PersonType"/>
    </xsd:complexType>
    
    <xsd:complexType name="HelloPersonServiceResponseType">
        <xsd:element name="Greetings" type="xsd:string"/>
    </xsd:complexType>

    <xsd:complexType name="PersonType">
        <xsd:sequence>
            <xsd:element name="FirstName" type="xsd:string"/>
            <xsd:element name="LastName" type="xsd:string"/>
        </xsd:sequence>
    </xsd:complexType>
</xsd:schema>

Now we’ll define a wsdl called helloPersonService.wsdl and we’ll store it in src/main/resources/wsdl

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions
    name="helloPersonService"
    targetNamespace="http://example.nl/hellopersonservice/1.0"
    xmlns:tns="http://example.nl/hellopersonservice/1.0"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    >
    <wsdl:types>
        <xsd:schema targetNamespace="http://example.nl/hellopersonservice/1.0">
            <xsd:import schemaLocation="../xsd/helloPersonService.xsd"
                        namespace="http://example.nl/hellopersonservice/1.0"/>
        </xsd:schema>
    </wsdl:types>
    <wsdl:message name="HelloPersonServiceRequest">
        <wsdl:part name="HelloPersonServiceRequest" element="tns:HelloPersonServiceRequest"/>
    </wsdl:message>
    <wsdl:message name="HelloPersonServiceResponse">
        <wsdl:part name="HelloPersonServiceResponse" element="tns:HelloPersonServiceResponse"/>
    </wsdl:message>
    <wsdl:portType name="HelloPersonServicePortType">
        <wsdl:operation name="greetPerson">
            <wsdl:input name="HelloPersonServiceRequest" message="tns:HelloPersonServiceRequest"/>
            <wsdl:output name="HelloPersonServiceResponse" message="tns:HelloPersonServiceResponse"/>
        </wsdl:operation>
    </wsdl:portType>
    <wsdl:binding name="HelloPersonServiceBinding" type="tns:HelloPersonServicePortType">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
        <wsdl:operation name="greetPerson">
            <soap:operation style="document" soapAction="http://example.nl/HelloPersonService/greetPerson"/>
            <wsdl:input name="HelloPersonServiceRequest">
                <soap:body use="literal"/>
            </wsdl:input>
            <wsdl:output name="HelloPersonServiceResponse">
                <soap:body use="literal"/>
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:service name="HelloPersonService">
        <wsdl:port name="HelloPersonServicePort" binding="tns:HelloPersonServiceBinding">
            <soap:address location="/service/helloPersonService" />
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>

Getting the pom right

Next up is adding the necessary component to the pom file. We need the jax-ws runtime libraries, the jetty plugin as we use jetty as the servlet container, and the jaxws-maven-plugin for generating the java code from the contract. I’ve highlighted these dependencies in the pom file below.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>nl.example</groupId>
    <artifactId>hello_person</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>hello_person Java EE 6 Webapp</name>
    <url>http://maven.apache.org</url>
    <repositories>
        <repository>
            <id>java.net2</id>
            <name>Repository hosting the jee6 artifacts</name>
            <url>http://download.java.net/maven/2</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>java.net2</id>
            <name>Repository hosting the jee6 artifacts</name>
            <url>http://download.java.net/maven/2</url>
        </pluginRepository>
    </pluginRepositories>
    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>com.sun.xml.ws</groupId>
            <artifactId>jaxws-rt</artifactId>
            <version>2.2</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.0.2</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.1-beta-1</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <configuration>
                    <webAppConfig>
                        <contextPath>hello_person</contextPath>
                    </webAppConfig>
                    <connectors>
                        <connector implementation="org.eclipse.jetty.nio.SelectChannelConnector">
                            <port>8083</port>
                        </connector>
                    </connectors>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>build-helper-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <id>add-source</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>add-source</goal>
                        </goals>
                        <configuration>
                            <sources>
                                <source>${basedir}/target/generated/src/main/java</source>
                            </sources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>jaxws-maven-plugin</artifactId>
                <version>1.12</version>
                <configuration>
                    <wsdlDirectory>${basedir}/src/main/resources/wsdl</wsdlDirectory>
                    <packageName>nl.example.hello_person.service.generated</packageName>
                    <keep>true</keep>
                    <sourceDestDir>${basedir}/target/generated/src/main/java</sourceDestDir>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>wsimport</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
        <finalName>hello_person</finalName>
    </build>
</project>

Writing the implementation

First, let’s do a Clean and Build in NetBeans. This will generate the JAXB/JAX-WS artifacts from the contract.
Notice that the jaxws-maven-plugin has generated an interface called nl.example.hello_person.service.generated.HelloPersonServicePortType for the PortType in the wsdl. This is the interface we have to implement. The code for the implementation is pretty straightforward. I’ve added it below for convenience. Notice that the implementation class is called nl.example.hello_person.service.HelloPersonServiceImpl. We’ll need this in one of the next steps. Also notice the necessary @WebService annotation. The endpointInterface atribute points of course to the generated interface.

package nl.example.hello_person.service;

import javax.jws.WebService;
import nl.example.hello_person.service.generated.HelloPersonServiceRequestType;
import nl.example.hello_person.service.generated.HelloPersonServiceResponseType;

@WebService(endpointInterface="nl.example.hello_person.service.generated.HelloPersonServicePortType")
public class HelloPersonServiceImpl implements nl.example.hello_person.service.generated.HelloPersonServicePortType {

    @Override
    public HelloPersonServiceResponseType greetPerson(HelloPersonServiceRequestType helloPersonServiceRequest) {
        HelloPersonServiceResponseType helloPersonServiceResponse = new HelloPersonServiceResponseType();
        helloPersonServiceResponse.setGreetings("Hello " + helloPersonServiceRequest.getPerson().getFirstName() + " " + helloPersonServiceRequest.getPerson().getLastName() + "!");
        return helloPersonServiceResponse;
    }

}

The web.xml file

Now, add a web.xml file to the project (you can do this via New File > Web > Standard Deployment Descriptor (web.xml)) and add the proper configuration for the jax-ws servlet to it:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
    <listener>
        <listener-class>
            com.sun.xml.ws.transport.http.servlet.WSServletContextListener
        </listener-class>
    </listener>
    <servlet>
        <description>JAX-WS endpoint</description>
        <display-name>The JAX-WS servlet</display-name>
        <servlet-name>jaxws</servlet-name>
        <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>jaxws</servlet-name>
        <url-pattern>/helloPersonService</url-pattern>
    </servlet-mapping>
</web-app>

The sun-jaxws.xml file

The last step is adding the sun-jaxws.xml file to the WEB-INF directory of the project. This is the content of the file:

<?xml version="1.0" encoding="UTF-8"?>
<endpoints
    xmlns='http://java.sun.com/xml/ns/jax-ws/ri/runtime'
    version='2.0'>
    <endpoint
        name='helloPersonService'
        implementation='nl.example.hello_person.service.HelloPersonServiceImpl'
        url-pattern='/helloPersonService' />
</endpoints>

Notice that the implementation attribute is pointing to our implementation class.

Running the service

Add jetty:run as custom action (via Run > Set Project Configuration > Customize > Actions) and run it.
Now open a browser and check the following url http://localhost:8083/hello_person/helloPersonService. If you see the following screen, the webservice is up and running!

Testing the service

For testing I’m using the soapUI plugin for Netbeans.
After you’ve installed the plugin, you can create a new Web Service Testing Project (New Project > SOA > Web Service Testing Project)

Point the initial WSDL property to the url you’ve seen on the Web Services page: http://localhost:8083/hello_person/helloPersonService?wsdl.

The testsuite now contains a greetPerson Test Step. You can now test if the service works. If all goes well, you should see something similar to the picture below.

Devoxx 2010 impressions – day 1

Last week I enjoyed 2 great University days at Devoxx. In this blog post i’ll comment on the 2 main talks i’ve visited on monday, i.e. Seam 3 and Spring Roo. Both talks had a lot in common.
In the past both were JEE frameworks which tried to fill a gap in traditional JEE development. The Spring framework simplified for one the whole entity bean fiasco of EJB2 by introducing a model layer based on POJOs and was typically a framework used for integration in the model layer of enterprise development. Springs lessons led to the EJB3 spec in JEE5 and the Managed Bean spec in JEE6.
Seam on the other hand filled the gap of integrating the view and the model layer, making it much easier to couple JSF pages to Session Beans (or their Seam counterparts). Its lessons have led to the CDI spec in JEE6 and (i think) to the EJB Lite spec.
Now, while both were considered a framework in the past, they are now in their latest incarnations both full blown stacks for Java Enterprise Development. And in this they have a lot in common. To name a few:

  • Both have their own (eclipse-)based IDE. For Spring it’s called SpringSource Tool Suite (STS), for Seam we have JBoss Developer Studio;
  • Both have solutions for cross layer concerns, like security, logging, SOAP/REST integration, event handling and AJAX support, to name a few;
  • Both have a solution for building the view layer. Spring has Spring MVC and Seam uses JSF, CDI and facelets;
  • And now both have their own RAD solution. In Spring it’s called SpringRoo, in Seam it’s called SeamForge.

The last 2 components of the stacks are (especially SeamForge) relatively new. Both are shell based and can also be used in Eclipse as a plugin (Of course, their own IDE’s have these plugins installed by default).

I think it’s a good thing that these stacks are emerging. In the end they will provide you with basically everything you need for Enterprise development. And in the end hopefully it will prevent all these hybrid solutions where everyone uses their favorite framework on the job, which hinders maintainability in the long run imho. The SpringSource stack and the JBoss stack are both big players and i honestly believe that commitment to these stacks by the development community will be an ongoing concern for many years to come.
If I had to pick a favorite among the two. I slightly prefer the JBoss stack, because it adheres much more to the JEE specification.

In the next 2 section i’ll summarize some highlights of the 2 talks.

Seam 3: State of the Union

by Pete Muir and Dan Allen

Seam 3 as apposed to the rather monolithic Seam 2 framework is of a modular design. It consists of a set of modules built on top of the core of Seam. This core consists of the reference implementation of CDI, Weld, which currently is supported for the JEE servers JBoss and Glassfish, and for the Web servers Tomcat and Jetty. It is also supported for a standalone JSE environment. Seam 3 is hosted at GitHub and uses Maven for the build lifecycle. There was also talk about the use of Gradle in the near future.

As i sad before. Seam’s 3 approach is more modular than that of its predecessor. Here’s a list of the modules that were presented during the talk:

  • Seam Solder, aka Weld extensions. This is the core of Seam 3 and Pete and Dan stressed that this is the module everyone should use. It provides to name but a few:
    • A lot of familiar Seam 2 annotations (eg. @Requires, @Unwraps);
    • Annotations for Logging;
    • Annotations for Resource handling;
    • Annotations to be used for queries in DAO’s;
  • SeamConfig, which extends the beans.xml configuration file for Seam related configurations in xml-style;
  • SeamInternational, for i18n an l10n support;
  • SeamFaces, which is an enhancement for JSF 2. It contains, to name a few, the familiar (Seam 2) @Conversation scope, support for multiple field validation, composite components and a simple front controller called ViewAction;
  • SeamPersistence, the module for declarative persistence, transactions, manual flushing and the likes;
  • SeamCath for error and event handling;
  • SeamServlet, which provides Servlet enhancements;
  • SeamRest, for REST integration;
  • SeamGWT, for GWT integration.

And already mentioned a big part of the presentation was reserved for a demonstration of the current state of the SeamForge project. It’s the successor to seam-gen. You can use it for RAD. It can generate an entire CRUD application out of the box based on a couple of simple database tables or defined entities. It is shell based and also comes as an IDE (Eclipse) plugin.  I was really impressed by the demo. As with seam-gen, you don’t have to mess with all the configuration files anymore, because you can let SeamForge do this for you. In no time you can generate a fully working demo application for your customers or one to be used as a reference for yourself for development. And SeamForge does this all in Maven style, which is big plus compared to the ANT style seam-gen, if i may say so. I can’t wait to try it all out for myself.

Another big topic was the Arquillian project. It contains for one an embedded container used for testing an enterprise application. Arquillian can be integrated in a testing framework like JUNIT or TestNG. Control of the container is handled via ANT tasks or a Maven plugin.

Extreme Productivity with Spring Roo

by Ben Alex and Stefan Schmidt

Spring Roo is a project within the SpringSource stack comparable to SeamForge. You can generate a CRUD application with it in no time in the Spring style. Like SeamForge, SpringRoo generates a Maven application, it can be controlled via highly intelligent shell commands and also comes with an Eclipse plugin.

It makes heavy use of AspectJ files. For example it puts the getter and setter methods of the generated JPA classes in a seperate AspectJ file per class. This keeps the JPA classes very clean. Another benefit is that the AspectJ classes can be generated (or even incrementally updated) so they can be omitted from the version control system. If you have the AspectJ plugin installed in Eclipse, code completion will work even for the code in the AspectJ files like those getter and setter methods. Of course STS comes with this plugin already installed.

The JPA classes that are generated by SpringRoo contain JPA 2 annotations and a couple of SpringRoo specific annotations.

Removing all of SpringRoo from your application can be done in three simple steps:

  1. Refactor the AspectJ code back in your Java code (can be done via the AspectJ plugin);
  2. Remove The SpringRoo annotations (search/delete);
  3. Remove the SpringRoo jar from you pom.xml.

Another part of the presentation showed the use of the Hades plugin for SpringRoo. With Hades you can generate a generic DAO layer on top of your JPA layer. Very powerful stuff.

The last big part of the presentation showed the view layer generation capabilities of SpringRoo. SpringRoo lets you generate a Spring MVC layer and takes care of a lot of scaffolding for you, again removing you of the burden of getting all those configuration files right. It eg. can take care of:

  • REST support
  • Templating support
  • Themes
  • i18n, l10n
  • Bean validation
  • Security
  • Spring WebFlow

All in all, this was the second time this day i was pretty impressed, and again I can’t wait to play with this stuff.

Using a nested property model in Apache Wicket for money validation

For one of my recent projects i needed to make sure that money fields were correctly translated from screen to database and vice versa.

The money fields are implemented as BigDecimal properties in the JPA model. The default Wicket behavior on input is to allow both the “.” and “,” characters and according to the active Locale to ignore the one that doesn’t correspond to the decimal seperator. This means that when the active Locale is the Dutch one (which uses the comma as the decimal seperator) and a user accidently enters €12.35 as the amount. It gets stored as €1235. This is very dangerous behavior.

The way we wanted the application to behave is to validate the input according to the Session Local and to disallow the entering of the wrong decimal seperator. There are probably a couple of way to do this, but the one i used involved using a custom validator and a nested property model.

Nested property model

The nested property model nests a BigDecimal model into a String model. Here’s the code.

package org.example.view.common;

import java.math.BigDecimal;
import org.apache.wicket.model.IModel;
import org.example.util.BigDecimalParser;
import org.example.view.format.BigDecimalToMoneySessionFormat;

public class DefaultMoneyModel implements IModel {

  private static final long serialVersionUID = 1479000955482442517L;
  private final IModel mNestedModel;

  public DefaultMoneyModel(IModel nestedModel) {
    mNestedModel = nestedModel;
  }

  public String getObject() {
    BigDecimal value = mNestedModel.getObject();
    // convert BigDecimal to String
    return BigDecimalToMoneySessionFormat.format(value);
  }

  public void setObject(String object) {
    // convert String to Bigdecimal
    BigDecimal value = BigDecimalParser.parseBigDecimal(object, getSessionLocale());
    mNestedModel.setObject(value);
  }

  public void detach() {
    mNestedModel.detach();
  }

  private Locale getSessionLocale() {
    // code to get the current Session Locale, omitted for brevity
    ....
  }
}

Custom validator

The custom validator i wrote first tries to parse the String to a BigDecimal just like Apache Wicket does and adds an extra check for the decimal separator.

package com.redwood.exchange.view.validator;

import java.math.BigDecimal;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.wicket.validation.IValidatable;
import org.apache.wicket.validation.validator.AbstractValidator;
import org.example.util.BigDecimalParser;

public class BigDecimalMoneyValidator extends AbstractValidator {

  private static final long serialVersionUID = 5638024896256902410L;

  @Override
  protected void onValidate(IValidatable validatable) {
    try {
      // First check that a normal conversion works
      @SuppressWarnings("unused")
      BigDecimal bigDecimal = BigDecimalParser.parseBigDecimal(
        validatable.getValue(), getSessionLocale());

      // Now check if the only non digit character is the decimal sign
      // corresponding to the locale and that it appears only once.
      checkDecimalSeparator(validatable.getValue(), getSessionLocale());
    } catch (Exception e) {
      error(validatable);
    }
  }

  private void checkDecimalSeparator(String value, Locale sessionLocale) {
    char decimalSeperator = new DecimalFormatSymbols(sessionLocale)
      .getDecimalSeparator();

    Pattern p = Pattern
      .compile("^\\d+(\\" + decimalSeperator + "\\d{2})?$");
    Matcher m = p.matcher(value);
    if (!m.find()) {
      throw new NumberFormatException("Invalid price.");
    }
  }

  private Locale getSessionLocale() {
    // code to get the current Session Locale, omitted for brevity
    ....
  }
}

Utility classes and resources

I need a parser class for parsing a String, i.e. user input, to a BigDecimal and a formatter class for presentation of the BigDecimal as a money field on the web page. Both should take the current Locale into account. Here’s the code for both classes.

package com.redwood.exchange.util;

import java.math.BigDecimal;
import java.util.Locale;
import org.apache.wicket.util.convert.converters.BigDecimalConverter;

public class BigDecimalParser {

  public static BigDecimal parseBigDecimal(String s, Locale locale) {
    BigDecimalConverter converter = new BigDecimalConverter();
    BigDecimal bigDecimal = (BigDecimal) converter.convertToObject(s,
      locale);
    return bigDecimal;
  }
}
package org.example.view.format;

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;

public class BigDecimalToMoneySessionFormat {

  public static String format(BigDecimal decimal, Locale locale) {
    if (decimal == null) {
      return "";
    }
    else {
      DecimalFormat decimalFormat = new DecimalFormat();
      decimalFormat.setMinimumFractionDigits(2);
      decimalFormat.setDecimalFormatSymbols(DecimalFormatSymbols
        .getInstance(locale));
      return decimalFormat.format(decimal);
    }
  }
}

Finally for displaying an error message i added a WicketApplication.properties file on the org.example.view level

BigDecimalMoneyValidator=The entered value is not a valid ${label}.

Adding it all to a Wicket Panel

Now that we have all the necessary code, let’s use it on a Wicket panel. Assume that we have a JPA entity class named Order with a property called price, which is a BigDecimal.

entering money

The money field is put on a html page corresponding to a Wicket panel via the following snippet:

  <span><div wicket:id="price" /></span>

The following code snippet from the corresponding Wicket Panel class will couple the component to the nested model and add the custom validator.

  TextField<String> priceField = new TextField<String>(
    "price", new DefaultMoneyModel(new PropertyModel<BigDecimal>(
    order, "price")));
  priceField.add(new BigDecimalMoneyValidator());

displaying money

The money field is put on a html page corresponding to a Wicket panel via the following snippet (in this case it’s put in a html table):

  <td wicket:id="price">EUR 12.95</td>

Now the following code in the corresponding Wicket panel class will couple the component to the nested model for use in a DataView.

  final DataView<Order> dataView = new DataView<Order>(..,..) {

    private static final long serialVersionUID = -6981777123077670308L;

    @Override
    public void populateItem(final Item<JPAEntity> item) {
      ..
      item.add(new Label("price", BigDecimalToMoneySessionFormat.format(item
        .getModelObject().getPrice())));
    }
  };

More info

For more info on using nested property models check out this excellent blog by Jeremy Tomerson.

Maven resource filtering not working

I recently ran into a problem when using maven resource filtering from within Eclipse. The problem was that properties in xml files (in this case a spring configuration file) weren’t expanded. Eventually i figured out what was causing the problem. That’s what this small blog is about.

Setup

As noted i wanted maven to expand some properties – datasource properties in this case – in a spring configuration file. The idea was to externalize the datasource connection information in various environment files, so that during deployment the right datasource connection information would be put in the spring configuration file according to the deployment environment, eg. local, test, staging.

For this i altered the spring conguration file, called spring-config.xml in my case, and changed the configuration for the dataSource spring bean into the one resembling the xml stanza below:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  <property name="driverClassName">
    <value>oracle.jdbc.driver.OracleDriver</value>
  </property>
  <property name="url">
    <value>jdbc:oracle:thin:@${db.host}:${db.port}:${db.sid}</value>
  </property>
  <property name="username">
    <value>${db.user}</value>
  </property>
  <property name="password">
    <value>${db.password}</value>
  </property>
</bean>

This file was stored in the default resource directory src/main/resources.

The property files were put in the src/main/filters directory and were called filter-<env>.properties, eg. the filter-local.properties file looked like this:

#spring-config.xml
db.host=localhost
db.port=1521
db.sid=XE
db.user=scott
db.password=tiger

In the pom file i added the following code necessary for the property expanding:

<build>
  <filters>
    <filter>src/main/filters/filter_${env}.properties</filter>
  </filters>
  <resources>
    <resource>
      <directory>src/main/resources</directory>
      <filtering>true</filtering>
    </resource>
  </resources>
</build>
....
<profiles>
  <profile>
    <id>env-local</id>
    <properties>
      <env>local</env>
    </properties>
  </profile>
  <profile>
    <id>env-test</id>
    <properties>
      <env>test</env>
    </properties>
  </profile>
....
</profiles>

Now i used eclipse with the m2eclipse plugin and defined the following configuration for packaging the project (a web application) to a war file suitable for my local environment:

Notice the embedded Maven Runtime which is selected by default.

Problem

After running the configuration the properties for the datasource bean were not expanded and the original spring configuration file for the local deployment got packaged. I also noticed that maven properties in property text files as opposed to xml files, did get expanded. So the problem only occurred in xml files.

Solution

I noticed that running the package maven goal from a terminal window did expand the properties. So eventually i realized that the embedded maven version selected by default was the cause of distress. After i switched the maven runtime for the run configuration to the maven version currently installed on my ubuntu distribution, the problem was solved. See the picture below for this configuration.

The maven version on my ubuntu distribution, by the way, was installed via the following command:

sudo apt-get install maven2

Note that in future versions the bug in the maven embedded runtime may be fixed.

Sortable and clickable lists with Apache Wicket

For my recent project I had to implement a sortable and clickable list. The project uses Apache Wicket as the front-end. It took me a while to get it all working and after some refactoring I was able to produce a generic solution to this problem. This solution is based on the following assumptions

  • The list to be sorted is based on a JPA entity and all the columns that need to be sortable are available as simple getter methods on the entity;
  • The entity has an id attribute, which uniquely identifies it;
  • The equals() and hasCode() methods of the entity will be overriden. Two entitiy instances will  be considered equal if their ids match;
  • The JavaScript jQuery library is available in the Wicket page displaying the list.

See the image below for an example of the eventual sortable and clickable list on which this blog is based.

The list shows the Account entity. All the columns are sortable and clicking on a row will open the url on which the hyperlink of the first column is based. There’s also a pagination link available in case the number of rows exceeds the number of rows displayed. In the remainder of this blog I’ll walk through all the necessary components.

Model

Interface ISortableEntity

For an entity to be able to be sorted in a list, I’ve defined the following interface:


package com.example.entity;

import java.math.BigDecimal;

public interface ISortableEntity {

 public BigDecimal getId();
 public void setId(BigDecimal id);
 public ISortableEntity newSearchInstance();

}

As you can see, the entity has to have an id attribute. It also has to implement the newSearchInstance() method which basically has to return a new instance of the entity class.

Entity Account

The list is based on an entity called com.example.entity.Account. The entity has to implement the ISortableEntity interface and has to override the equals() and hashCode() methods. As mentioned before two entities are considered equal if their ids match.

package com.example.entity;

import java.math.BigDecimal;
import javax.persistence.*;

@Entity
@Table(name = "ACCOUNTS")
@SequenceGenerator(allocationSize = 1, name = "account_id_generator", sequenceName = "accounts_id_seq")
public class Account implements java.io.Serializable, ISortableEntity {

	private static final long serialVersionUID = -1213397204916173215L;
	private BigDecimal id;
	private BigDecimal accountNumber;
	private String firstName;
	private String lastName;
	private String username;

	public Account() {
	}

	public Account(BigDecimal id) {
		this.id = id;
	}

	public Account(BigDecimal id, BigDecimal accountNumber, String firstName,
			String lastName, String username) {
		this.id = id;
		this.accountNumber = accountNumber;
		this.firstName = firstName;
		this.lastName = lastName;
		this.username = username;
	}

	@Id
	@GeneratedValue(generator = "account_id_generator")
	@Column(name = "ID", unique = true, nullable = false, precision = 22, scale = 0)
	public BigDecimal getId() {
		return this.id;
	}

	public void setId(BigDecimal id) {
		this.id = id;
	}

	@Column(name = "ACCOUNT_NUMBER", precision = 22, scale = 0)
	public BigDecimal getAccountNumber() {
		return this.accountNumber;
	}

	public void setAccountNumber(BigDecimal accountNumber) {
		this.accountNumber = accountNumber;
	}

	@Column(name = "FIRST_NAME", length = 100)
	public String getFirstName() {
		return this.firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	@Column(name = "LAST_NAME", length = 100)
	public String getLastName() {
		return this.lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	@Column(name = "USERNAME", length = 50)
	public String getUsername() {
		return this.username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Account other = (Account) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		return true;
	}

	public ISortableEntity newSearchInstance() {
		ISortableEntity searchAccount = new Account();
		return searchAccount;
	}
}

View

For the view layer the sortable list of accounts will be displayed on a Wicket Panel. For this we’ll need a template and the corresponding Java class. For the list to be sortable we first have to extend the abstract class org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider. For one of its methods, we also need to extend the abstract class org.apache.wicket.model.LoadableDetachableModel. I’ve generified both of these subclasses so they only depend on the aforementioned interface ISortableEntity. The code for these classes is shown below.

SortableEntityProvider

package com.example.view.common;

import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.PropertyModel;

import com.example.entity.ISortableEntity;

public class SortableEntityProvider<T extends ISortableEntity> extends
		SortableDataProvider<T> {
	private static final long serialVersionUID = -1646363525627999438L;
	private List<T> data;

	protected List<T> getData() {
		return data;
	}

	protected void setData(List<T> data) {
		this.data = data;
	}

	public SortableEntityProvider(List<T> data, String defaultSort) {
		super();
		setData(data);
		// set default sort
		setSort(defaultSort, true);
	}

	public Iterator<? extends T> iterator(int first, int count) {
		final SortParam sp = getSort();

		Collections.sort(getData(), getComparator(sp));

		return getData().subList(first, first + count).iterator();
	}

	public int size() {
		return getData().size();
	}

	public IModel<T> model(T object) {
		return new DetachableEntityModel<T>(object, getData());
	}

	protected Comparator<T> getComparator(final SortParam sp) {
		return new Comparator<T>() {
			public int compare(T arg0, T arg1) {

				int result;

				PropertyModel<Comparable> model0 = new PropertyModel<Comparable>(
						arg0, sp.getProperty());
				PropertyModel<Comparable> model1 = new PropertyModel<Comparable>(
						arg1, sp.getProperty());

				if (model0.getObject() == null && model1.getObject() == null) {
					result = 0;
				} else if (model0.getObject() == null) {
					result = -1;
				} else if (model1.getObject() == null) {
					result = 1;
				} else {
					result = model0.getObject().compareTo(model1.getObject());
				}

				if (!getSort().isAscending()) {
					result = -result;
				}

				return result;

			}
		};
	}
}

The constructor takes a list with all the rows displayed on the panel (usually a list of entities returned by a service calling a DAO) as the data parameter, along with a string representation of the attribute providing the default sort, eg. “firstName” for default sorting on the First Name of the Account.

DetachableEntityModel

package com.example.view.common;

import java.math.BigDecimal;
import java.util.List;

import org.apache.wicket.model.LoadableDetachableModel;

import com.example.entity.ISortableEntity;

public class DetachableEntityModel<T extends ISortableEntity> extends LoadableDetachableModel<T> {
	private static final long serialVersionUID = -2133620119433783150L;

	private final ISortableEntity entity;
	private final List<T> data;

	public DetachableEntityModel(ISortableEntity a, List<T> data) {
		this.entity = a;
		this.data = data;
	}

	public int hashCode() {
		return getId().hashCode();
	}

	public BigDecimal getId() {
		return entity.getId();
	}

	public boolean equals(final Object obj) {
		if (obj == this) {
			return true;
		} else if (obj == null) {
			return false;
		} else if (obj instanceof DetachableEntityModel) {
			DetachableEntityModel other = (DetachableEntityModel) obj;
			return other.getId() == getId();
		}
		return false;
	}

	protected T load() {
		ISortableEntity searchEntity = entity.newSearchInstance();
		searchEntity.setId(getId());
		return data.get(data.indexOf(searchEntity));
	}
}

AccountsPanel.html

<html>
<body>
<wicket:panel>
  <h2>Accounts</h2>
  <table class="clickable">
    <thead>
      <th wicket:id="orderByAccountNumber">[Account number]</th>
      <th wicket:id="orderByFirstName">[First name]</th>
      <th wicket:id="orderByLastName">[Last name]</th>
      <th wicket:id="orderByUsername">[Username]</th>
    </thead>
    <tbody>
      <tr wicket:id="accountRow">
        <td><a wicket:id="id" href="#"><span wicket:id="idSpan">[id]</span></a></td>
        <td wicket:id="firstName">[First name]</td>
        <td wicket:id="lastName">[Last name]</td>
        <td wicket:id="username">[Username]</td>
      </tr>
    </tbody>
  </table>
  <br />
  <span wicket:id="navigator">[dataview navigator]</span>
</wicket:panel>
</body>
</html>

The css class clickable will take care of the styling of the table and is coupled to the jQuery code (shown later on) that will implement the row clicks. A row click will make sure that the hyperlink coupled to the first column in the table will be clicked. The page also contains a component that will take care of the pagination.

AccountsPanel.java

package com.example.view.common;
....
public class AccountsPanel extends Panel {
 private static final long serialVersionUID = -395786600454932830L;

 @SpringBean
 AccountService accountService;

 public AccountsPanel(String id) {
 super(id);

 drawChildren();
 }

 private void drawChildren() {

 List<Account> accounts = accountService.getAccounts();

 SortableDataProvider<Account> dp = new SortableEntityProvider<Account>(
 accounts, "lastName");

 final DataView<Account> dataView = new DataView<Account>(
 "accountRow", dp) {
 private static final long serialVersionUID = 6364718764586838158L;

 @Override
 public void populateItem(final Item<Account> item) {
 item.add(new SimpleAttributeModifier("class",
 item.getIndex() % 2 == 0 ? "even" : "odd"));

 Link<Account> idLink = new Link<Account>("id") {
 private static final long serialVersionUID = -480222850475280108L;

 @Override
 public void onClick() {
 //TODO: Do stuff
 }
 };
 idLink.add(new Label("idSpan", item.getModelObject().getId()
 .toPlainString()));
 item.add(idLink);

 item.add(new Label("firstName", item.getModelObject()
 .getFirstName()));
 item.add(new Label("lastName", item.getModelObject()
 .getLastName()));
 item.add(new Label("username", item.getModelObject()
 .getUsername()));
 }
 };

 dataView.setItemsPerPage(5);

 addSorting(dp, dataView);

 add(dataView);

 add(new PagingNavigator("navigator", dataView));
 }

 private void addSorting(SortableDataProvider<Account> dp,
 final DataView<Account> dataView) {
 add(new SortableOrderBy<Account>("orderByAccountNumber", "id", dp,
 dataView));
 add(new SortableOrderBy<Account>("orderByLastName", "lastName", dp,
 dataView));
 add(new SortableOrderBy<Account>("orderByFirstName", "firstName", dp,
 dataView));
 add(new SortableOrderBy<Account>("orderByUsername", "username", dp,
 dataView));
 }

}
  • lines 6,7,17: Service (in this case a Spring Bean) that will cough up the list of accounts for display;
  • line 19,20: Here the SortableDataProvider is initialized with the accounts list. The accounts will be sorted on “lastName” by default;
  • line 22,23: The SortableDataProvider is fed to a DataView coupled to the accountRow table row wicket component;
  • line 36: This will be the code executed when the row is clicked;
  • line 52: The list displayed will contain 5 accounts;
  • line 54: Here the table row headers are added. They will be clickable for sorting the corresponding table column they belong to. I’ve created a small subclass for OrderByBorder, called SortableOrderBy so I don’t have to repeat the code that resets the pagination upon sort (the code will be provided below). Note that the second argument of the SortableOrderBy constructor is couple to the getter method on the Account entity. You can also apply sorting to properties of a related entity. Let’s presume that the Account entity has a relationship with an Address entity. If you would like to display say the city of the Address in the Accounts list and make it sortable, the second argument would be address.city ;
  • line 58: Here the paginator for the list is added to the panel.

SortableOrderBy.java

package com.example.view.common;

import org.apache.wicket.extensions.markup.html.repeater.data.sort.ISortStateLocator;
import org.apache.wicket.extensions.markup.html.repeater.data.sort.OrderByBorder;
import org.apache.wicket.markup.repeater.data.DataView;

public class SortableOrderBy<T> extends OrderByBorder {

  private static final long serialVersionUID = 5017011967951860645L;
  private final DataView<T> dataView;

  public SortableOrderBy(String id, String property,
      ISortStateLocator stateLocator, DataView<T> dataView) {
    super(id, property, stateLocator);
    this.dataView = dataView;
  }

  @Override
  protected void onSortChanged() {
    dataView.setCurrentPage(0);
  }
}

As I said, this is just a simple subclass to reset the current page to the first one after the sorting has been applied.

css

.clickable tr.even {
    background-color:#E9F9FF;
}

.clickable tr:hover {
    background-color: #F0F0F0;
}

.clickable td:hover {
    cursor: pointer;
}

I’m just showing the most important style elements. The above will make sure that:

  • the odd and even rows of the table are of different color;
  • the row that you hover over will change its color;
  • the arrow cursor will be replaced by a pointer cursor when you hover over a clickable row;

jQuery

The last piece of the puzzle is a small piece of jQuery code that will make sure the correct styling is applied and that a row click will lead to the clicking of the corresponding hyperlink programmatically.

$(document).ready(function() {

    $('.clickable tr').click(function() {
        var href = $(this).find("a").attr("href");
        if(href) {
            window.location = href;
        }
    });

})

And that’s all there is to it. With the code shown in this blog you can transform any list of entities into a sortable and clickable one.

%d bloggers like this: