Saturday, April 2, 2011

Grails 1.3.6 and JBossWS work well together

JBossWS may not be the first option that comes to mind when you think of adding SOAP web services to your grails application, but if you are deploying to a JBoss server, this choice works very well and should be considered.

I will describe how to build a PingService using a bottom-up approach. The ping service has one method, greet, which takes a string argument representing a name and returns a friendly greeting.

The example is very simple and the solution is more complicated than it needs to be in order to show how java code (the JBossWS method) can call a Grails Service. In a real world scenario, this would be a suitable approach.

We will begin by creating a Grails Application named demo:

grails create-app demo

Next we will create a Grails Service named PingService:

grails create-service com.javabilities.demo.ws.Ping

Despite the name, this class is a grails service (spring bean) and not a web service. This method will be called by the web service.

class PingService {
static transactional = true

String greet(String name) {
"Hello ${name}!"
}
}

Create an annotated POJO named Ping in the com.javabilities.demo.ws package of the src/java directory:

@WebService(targetNamespace = "https://www.javabilities.com/demo/services",
serviceName = "PingService")
@SOAPBinding(style = SOAPBinding.Style.RPC)
public class Ping {
}

Add the annotated greet method:

@WebMethod
@WebResult(name="greeting")
public String greet(@WebParam(name="name") String name) {
}

NOTE: the WebResult annotation gives a meaningful name to the return variable in the generated WSDL.

Now the greet method in the JBossWS need to call the Grails PingService. Add the following to the greet method of the JBossWS:

ApplicationContext ctx = (ApplicationContext)ApplicationHolder.getApplication().getMainContext();
PingService service = (PingService)ctx.getBean("pingService");
String greeting = service.greet(name);
return greeting;

This retrieves the spring context from the Grails ApplicationHolder, then the PingService spring bean from the spring context and then finally calls the greet method on the PingService spring bean.

We are done with the coding now, we just need to define the servlet and its mapping in the web.xml. Grails generates the web.xml from a template, so we will need to run the grails install-templates target:

grails install-templates

You will notice a new src/templates directory. You can remove the artifacts and scaffolding folders underneath the templates directory.

Add servlet and servlet mapping to the src/templates/web.xml file:

<servlet>
<servlet-name>PingService</servlet-name>
<servlet-class>com.javabilities.demo.ws.Ping</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PingService</servlet-name>
<url-pattern>/services/PingService</url-pattern>
</servlet-mapping>

We can now deploy our Grails application to a JBoss AS configuration.

After you have deployed your web application, you can view the WSDL for your web application at:

http://localhost:8080/demo/services/PingService?wsdl

and access the web service at:

http://localhost:8080/demo/services/PingService

What we have accomplished is that we have mixed the conveniences of JBossWS and JBoss AS with the conveniences of the Groovy language and the Grails framework and have quickly created a web service. This example also shows how well Groovy and Grails work in the Java world.

The disadvantage of this approach is that when we run the application from our IDE, we will not be able to test our web services because of our dependency on the JBossAS to generate our web services. We can get around this by just using/testing the web services by calling the grails service methods when running in development mode.

Sunday, November 28, 2010

How to Deploy a grails 1.3.5 webapp to JBoss 5.1.0.GA

There are a few problems that prevent grails deploying to the JBoss application server. I will describe specifically how to deploy a grails 1.3.5 web application to the JBoss 5.1.0.GA Applicaton Server community edition.

A bug has been filed on the grails Jira server describing the problem. Please create a Jira account and vote for this issue so that it may get more attention. The issue is Grails-6953.


The first issue is that newer versions of hibernate require hibernate validator even though grails doesn't need it. This is Jira issue Grails-5606 and says that it was fixed in grails 1.2.2 but wasn't.

In the text of Grails-5606 there was a work around described by Graeme Rocher that solves the issue. The work around is:

In BuildConfig.groovy:
  1. Uncomment the JBoss maven repository
  2. Add the hibernate-validator dependency
runtime('org.hibernate:hibernate-validator:3.1.0.GA') {
excludes 'slf4j-api', 'hibernate.core', 'hibernate-comnmons-annotations', 'hibernate-entitymanager'
}


The second issue is that grails includes logging libraries that JBoss already includes. JBoss complains about this. There is a good post covering that issue here. The logging libraries can be removed from the grails war file by adding the following to the end of the BuildConfig.groovy file:

// Remove logging artifacts from war
grails.war.resources = { stagingDir ->
def toRemove = [
"$stagingDir/WEB-INF/lib/log4j-1.2.16.jar",
"$stagingDir/WEB-INF/lib/commons-logging.jar",
"$stagingDir/WEB-INF/lib/commons-logging-1.1.jar",
"$stagingDir/WEB-INF/lib/jcl-over-slf4j-1.5.8.jar",
"$stagingDir/WEB-INF/lib/slf4j-api-1.5.8.jar",
"$stagingDir/WEB-INF/lib/slf4j-log4j12-1.5.8.jar"
].each {
delete(file: it)
}
}


The third issue has to do with how grails creates the filename of the war. Grails constructs the war filename based on properties defined in application.properties. The war filename is app.name-app.version.war. When deploying the grails web application to an environment for testing, we may not want to access the application like:

http://mysite.com/myapp-0.1/

To get around this issue, we can create a jboss-web.xml file in the web-app/WEB-INF folder. The contents of the jboss-web.xml file are:

<jboss-web>
<context-root>/wis</context-root>
</jboss-web>

Monday, June 22, 2009

Access a Legacy Database using GORM DSL

  • Introduction
Many tutorials and posts discuss creating green-field grails application. In this post, I am going to cover creating a simple grails CRUD application using an existing legacy database schema using GORM DSL. This article assumes that you understand Oracle database, grails and GORM basics. Follow on posts may discuss topics such as testing the GORM DSL mappings, adding access control and creating simple reports.
NOTE: I got a lot of the information for this post from the "Advanced GORM kung fu" chapter of the Grails in Action book from Manning. I highly recommend this book.
  • Environment
In creating this post, I am using the netbeans 6.7 RC3 IDE, Sun JDK 1.6.0_14, groovy 1.6.3, grails 1.1.1 and Oracle XE on an Ubuntu workstation. However, the code I am showing should work just fine on any OS/IDE/Oracle combination. I have chosen the Oracle XE database because it comes with several example database schemas in a freely available database and Oracle is a database that I often work with professionally.

After you have installed the Oracle XE database: 1) enable the hr user and 2) change it's password.

1) ALTER USER hr ACCOUNT UNLOCK;
2) ALTER USER hr IDENTIFIED BY your_hr_password;

Verify that you can successfully connect to the hr database with you new password via sqlplus, SQL Developer or some other tool.

Here is a diagram of the hr database:


  • Create the Application
I will start by creating domain classes for just 3 of the tables, the Region, Country and Location tables. These tables are more straight forward and we can cover the other tables in another post.
  1. Create the grails application:
    grails create-app hrReports

  2. Copy the Oracle JDBC driver ojdbc14.jar to hrReports/lib. The Oracle JDBC Driver can be copied from $ORACLE_HOME/jdbc/lib or downloaded from the Oracle site.

  3. Create the grails domain classes Region, Country and Location:
    grails create-domain-class Region
    grails create-domain-class Country

  4. Create the grails controller Region, Country and Location:
    grails create-controller Region
    grails create-controller Country
  • Configure our DataSource
Modify our DataSource.groovy and add something similar to the following as your development environment:

development {
dataSource {
driverClassName = "oracle.jdbc.OracleDriver"
url = "jdbc:oracle:thin@localhost:1521:xe
username = "hr"
password = "your_hr_password"
}
}
  • Configure the Scaffolding
In the RegionController and CountryController classes add:

def scaffolding = true

Our front end development is complete.
  • Create the Region Domain Object
The Regions table has only two fields: REGION_ID NUMBER and REGION_NAME VARCHAR(25). This is represented in our Region Domain class:

Integer regionId
String regionName

The constraints are:

static constraints = {
regionId(blank:false, unique:true)
regionName(nullable:true, maxSize(25)
}

Since the Region Domain Class is mapped to the REGIONS database table, we need to add the following mapping:

static mapping = {
table 'REGIONS'
}

REGION_ID is the primary key and it is assigned by the user and not by a sequence or some other method. Additionally, hibernate expects the primary key to be named id. Hibernate also uses a field named version (which the DBA will not allow us to add to our legacy database) for optimistic locking. We address both of these issues by adding the following to our mapping closure:

version false
id generator:'assigned', column:'REGION_ID', type:'integer'

Now comes the funky part; dealing with a natural key as a primary key. We want to refer to the primary key as regionId in our groovy code and we also want the field to appear in the grails generated scafolding. We can accomplish this by changing the variable name from regionId to id (so hibernate will work) and create a transient variable named regionId. We create a getter and setter for regionId to tie it to the id field:
Integer id
//regionId is the primary key
static transients = ['regionId']
void setRegionId(Integer regionid) {
id = regionId
}
void getRegionid() {
return id
}
This change allows hibernate to work (it has an id), the code to reference regionID and the mapping from the GORM DSL to Database columns to work as expected. If we are inserting a new record with this approach, we need to user the save(insert:true) since a non-null id indicates a persisten instance.

Pull it all together and our Region.groovy Domain Class looks like this:

class Region {
Integer id
// regionId is the primary key
static transients = ['regionId']
void setRegionId(Integer regionId) {
id = regionId
}
Integer getRegionId() {
return id
}
String regionName

static constraints = {
id(blank:false, unique:true)
regionName(nullable:true, maxSize:25)
}

static mapping = {
table 'REGIONS'
version false
id generator:'assigned', column:'REGION_ID', type:'integer'
}

String toString() {
"$regionName"
}
}
  • Create the Country Domain Object
The Country Domain Object if very similar to the Region Domain Object. There are two primary differences:
  1. The primary key is a String and not an Integer
  2. There is a one-to-many relationship that is represented by the region field.
Here is the finished Country.groovy Domain Class:

class Country {
String id
// countryId is the primary key
static transients = ['countryId']
void setCountryId(String countryId) {
id = countryId
}
String getCountryId() {
return id
}
String countryName
Region region

static constraints = {
id(blank:false, unique:true, maxSize:2)
countryName(nullable:true, maxSize:40)
region(nullable:true)
}

static mapping = {
table 'COUNTRIES'
version false
id column:'COUNTRY_ID', generator:'assigned', type:'string'
}

String toString() {
"$countryName"
}
}
  • Conclusions
  1. If you made it this far, you really do care about GORM legacy DB mappings.
  2. It takes longer to write this blog post than to figure out and write the CRUD application using the GORM DSL mappings.
  3. It does seem to be possible to quickly map legacy databases with GORM DSL.
  4. This was completed in a couple of hours and in 224 LOC (not counting Unit Tests). I think the next times will be much quicker.
  • Next Steps
I can make available the Domain Classes for the rest of the HR schema. Also, I would like to write more thorough Unit Tests to verify that the GORM DSL mappings are doing what we expect. I think normally this shouldn't be necessary and we can expect our ORM tools to do what they are supposed to, but in this case, it will help to reassure myself that the GORM DSL mappings are doing what I expect them to be doing.