Showing posts with label BlazeDS. Show all posts
Showing posts with label BlazeDS. Show all posts

Saturday, May 23, 2009

Converting a Java/Flex application to Grails/Flex

I have a small reference application to CRUD ( create, read, update and delete ) notes from a back end service using a simple flex application. This reference application was originally written with Spring 2.5, later upgraded to 3, and Hibernate 3.x with MySql 5. As new versions of these technologies come out, I integrate it into this simple reference application. I use it as a sanity check while integrating all of those technologies because the application is not too difficult.

I decided to try to migrate it to a Grails platform to get a sense for how difficult that might be. I am going to share the steps that I took to migrate my Tomcat/Spring3/Hibernate3/MySql5 application to Grails/MySql5.

Since I already had a database schema from the Java implementation I decide to leverage that DDL so that both applications could run against the same backend.

Since I knew that the user interface was a Flex interface, I installed the flex plugin.

grails install-plugin flex
This plugin allows one to expose a Grails service as a Flex BlazeDS service by simply adding: static expose = ['flex-remoting'] to the service.

The 3 methods that the Java service exposed were:
* getAllNotes
* saveOrUpdateNote
* deleteNode

Therefore, to maintain the same Flex UI I needed to expose the same API.

The service ultimately ended up looking like the following:
package com.redpointtech.services
import com.redpointtech.domain.Note
class RedpointNotesService {

boolean transactional = true
static expose = ['flex-remoting']

def getAllNotes() {
def allNotes = Note.findAll()
return allNotes
}

def saveOrUpdateNote(Note note) {
def noteid = note.id
println "NOTE ID TO SAVE: " + noteid
// until I can figure out how to get unsavedValue:0 to
// work I need to do my own unsaved value.
if( noteid == 0 ) {
note.id = null;
}
note.save()
}

def deleteNote(Long noteId) {
def note = Note.findById(nodeId)
note?.delete()
}
}



Take special note of the check for a zero noteid. I could not immediately figure out how to tell grails that the 'unsaved-value' is 0 instead of the default null, so I had to make that programmatic. I am still looking into this, but if you know the answer please leave a comment.

To my pleasant surprise, the service was very small and the only extra work to expose the service to Flex was to add the 'static expose = ['flex-remoting']'. Anyone that has exposed Flex services knows that you have to add a servlet to the web.xml, update the flex-remote.xml, add a Spring factory to get the service. While not hard, certainly more work that added one line to a service. Please note, that I did NOT update the web.xml at all.

Next I turned my attention to the Note domain object. I used the the grails create-domain command to create the Note domain object.

To keep this as minimal as possible to start I did not add any constraints - clearly you would normally.

The Note domain class just keeps track of the authors name, the text of the note and the date the note was created. There is no mapping to a User domain object because I purposefully wanted to keep this reference application very simple.

Below is the Grails domain object:
package com.redpointtech.domain
class Note{

//Long id
String author
String text
Date createDate

static constraints = {
}

static mapping = {
table 'notes'
version false
id unsavedValue: 0
text column: "note_text"
createDate column: "create_date"
}

}



There are a couple of aspects to note:
* I commented out the id property because by convention Grails gives every domain object an id property.
* I had to add a mapping section because I was not able to use the grails default values.

If you look up at the service class again, you will see methods on the Note class like 'findAll' but you do not see them here. This is because GORM, which is Grails' Object Relational Mapping which sits on top of hibernate, creates many of the standard methods for you and dynamically adds them at runtime. This dramatically decreases the number of DAO methods you need to write - and really all but eliminates the DAO layer all together.

At this point, I now I have a service and a domain object that represents the Notes. Because I am using a MySql database and not the supported out of the box HSQL DB, I have a little more configuration to do.

The grails-app/conf/DataSource.groovy file contains the datasource definitions for all of the environments.

Below is the version of DataSource.groovy that I used:
dataSource {
pooled = true
driverClassName = "org.hsqldb.jdbcDriver"
username = "sa"
password = ""
}
hibernate {
cache.use_second_level_cache=true
cache.use_query_cache=true
cache.provider_class='com.opensymphony.oscache.hibernate.OSCacheProvider'
}
// environment specific settings
environments {
development {
dataSource {
pooled = false
driverClassName = "com.mysql.jdbc.Driver"
dbCreate = "update" // one of 'create', 'create-drop','update'
url = "jdbc:mysql://localhost/test"
username = "root"
password = "root"
logSql="true"
dialect = org.hibernate.dialect.MySQL5InnoDBDialect
}
hibernate {
show_sql = true
hibernate.format_sql = true
}
}
hsqldevelopment {
dataSource {
dbCreate = "create-drop" // one of 'create', 'create-drop','update'
url = "jdbc:hsqldb:mem:devDB"
}
}
test {
dataSource {
dbCreate = "update"
url = "jdbc:hsqldb:mem:testDb"
}
}
production {
dataSource {
dbCreate = "update"
url = "jdbc:hsqldb:file:prodDb;shutdown=true"
}
}
}

You can see in the environments section, I altered the development datasource to use MySql instead of HSQL. This should be pretty standard datasource configuration for MySql.

The last thing I needed to do was drop the mysql JDBC jar into the lib directory - and it was complete.

In the end I went from absolute zero, to a working Grails application that gave me the same functionality as my Tomcat/Spring/Hibernate application in less than an hour. I have to say, I was very impressed with the speed of development in the grails environment.

My company is currently working on a full featured, consumer facing Flex/Grails application and Grails has still not let me down even in this more challenging real world environment.

Every time I use Grails, I am more convinced of its viability as a production ready framework. It probably helps that it is built upon production ready frames of Spring and Hibernate.

Sunday, January 25, 2009

Spring Blaze Integration 1.0 M1 with Hibernate

I have a reference project that I have been using to stitch the different Spring/Hibernate/Flex/AIR technologies together.

Well today, I wanted to upgrade to Spring3 and look at the new Spring BlazeDS Integration. You can find all of the information about the Spring BlazeDS integration here.

This particular project used as Flex 3 front end, communicating to a Tomcat/Spring2.5/SpringJDBC to MySql database. Today I decided to upgrade the application to use Spring3 and Hibernate 3.3.1 which I did not anticipate being a big project - but I was wrong. Well ok, it was not a big project but it was bigger than I expected it to be.

If you following along with the sample application from the Spring/BlazeDS integration it goes really well.

However when you go to integrate Hibernate and use the OpenSessionInViewFilter things get a little tricky.

Conventional Spring wisdom would have us use the ContextLoaderListener to load the Spring context much like the following:

    <context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/main-application-config.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>



However, when I did that the MessageBroker complained that the ServletConfig was null so I decided I would initialize the Spring application context as the Spring BlazeDS Integration suggested. However - when I did that, the OpenSessionInView Filter complained that the web application context was not initialized.

So I was between the proverbial rock and a hard place.

I spent some time looking through the code to see how I could 'fix' this problem. Then it occurred to me that perhaps it was not a problem with the code. Maybe, this could fixed with a different configuration. The OpenSessionInView filter just needs information about the Hibernate Session, SessionFactory, DataSource, etc. It does not need to know about the MessageBroker and the MessageBroker does not need the Hibernate information.

So instead of trying to fix the code, I looked at how to fix my configuration.

I looked at my application config, and removed all of the elements that were related to Blaze integration and created a separate configuration for the servlet to load. I now have the ContextLoaderListener initialize part of the application config and the servlet the other part.

Here is a portion of the web.xml file configuration:

    <context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/main-application-config.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>


<filter>
<filter-name>hibernate-session</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
<init-param>
<param-name>sessionFactoryBeanName</param-name>
<param-value>sessionFactory</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>hibernate-session</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<servlet>
<servlet-name>redpointnotes</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/blaze-config.xml</param-value>
</init-param>

<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>redpointnotes</servlet-name>
<url-pattern>/spring/*</url-pattern>
</servlet-mapping>




The blaze-config.xml file looks like the following:
  <bean id="mySpringManagedMessageBroker" 
class="org.springframework.flex.messaging.MessageBrokerFactoryBean" />

<bean id="flexMessageBroker" abstract="true">
<property name="messageBroker" ref="mySpringManagedMessageBroker" />
</bean>

<!-- bean id name is the name that will be exposed to Flex remoting as the destination name -->
<bean id="redpointNotesServiceDestination" parent="flexMessageBroker"
class="org.springframework.flex.messaging.remoting.FlexRemotingServiceExporter">
<property name="service" ref="notesService" />
</bean>

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />

<!-- map request paths at /messagebroker to the BlazeDS MessageBroker -->
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<value>
/messagebroker/*=mySpringManagedMessageBroker
</value>
</property>
</bean>

<!-- dispatches requests mapped to a message broker -->
<bean class="org.springframework.flex.messaging.servlet.MessageBrokerHandlerAdapter" />




So by breaking up the configuration I was able to get around this 'chicken-n-egg' problem but I will still look to see if there is a more elegant way to handle this. If anyone has a better way of dealing with this, please post a comment so we can all benefit.

I hope this helps save someone some time.