Skip to content

Spring Roo and GWT: Like Peanut Butter and Chocolate

May 20, 2010

One of the more interesting announcements to come out of Google I/O is that Spring Roo now supports integration with the hot-off the-pres GWT 2.1 M1 release. GWT 2.1 supports new data presentation widgets and adds formal support for the MVP framework.

I had not heard of Roo before the Google announcement – and downloaded it to give it quick spin. It passes the 10 minute test with flying colours.

Roo addresses the biggest  GWT pain point:  Namely, that there is a lot of tedious boilerplate code required for a GWT application (DTO objects, views, presenters,etc.). Roo does a nice job of generating the majority of this code and supports further modifications and “round tripping” to keep everything in sync.

Here is an example using the Roo shell where we take an existing entity (called “Person”) and add a new date field (“lastUpdate”) to the entity. Notice how Roo modifies the existing domain and view objects.

focus --class ~.domain.Person
~.domain.Person roo> hint
At this stage of the project, you have a few options:

  * List all hint topics via 'hint topics'
  * Create more fields with 'hint fields'
  * Create more entities with 'hint entities'
  * Create a web controller with 'hint controllers'
  * Create dynamic finders with 'hint finders'
  * Setup your logging levels via 'hint logging'
  * Run tests via Maven (type 'perform tests')
  * Build a deployment artifact (type 'perform package')
  * Learn about Eclipse integration by typing 'hint eclipse'
  * Add support for Google Web Toolkit via 'hint gwt'
  * Discover all Roo commands by typing 'help'
~.domain.Person roo> field

field boolean          field date             field email template   field enum             field number           field other
field reference        field set              field string
~.domain.Person roo> field date --

~.domain.Person roo> field date --fieldName lastUpdate --type java.util.Date
Managed SRC_MAIN_JAVA/com/my2do/idm/domain/Person.java
Managed SRC_MAIN_JAVA/com/my2do/idm/domain/Person_Roo_JavaBean.aj
Managed SRC_MAIN_JAVA/com/my2do/idm/domain/Person_Roo_ToString.aj
Managed SRC_TEST_JAVA/com/my2do/idm/domain/PersonDataOnDemand_Roo_DataOnDemand.aj
Managed SRC_MAIN_JAVA/com/my2do/idm/gwt/scaffold/generated/PersonListView.java
Managed SRC_MAIN_JAVA/com/my2do/idm/gwt/scaffold/generated/PersonDetailsView.java
Managed SRC_MAIN_JAVA/com/my2do/idm/gwt/scaffold/generated/PersonDetailsView.ui.xml
Managed SRC_MAIN_JAVA/com/my2do/idm/gwt/scaffold/generated/PersonEditView.java
Managed SRC_MAIN_JAVA/com/my2do/idm/gwt/scaffold/generated/PersonEditView.ui.xml
Managed SRC_MAIN_JAVA/com/my2do/idm/gwt/request/PersonRecord.java
~.domain.Person roo>

What isn’t obvious from the above example is that the Roo shell has smart TAB completion everywhere. It is super easy to navigate and figure out what options a command takes.  The ~ (tilde) notion you see above is short hand for the root package name.

Roo generates a vanilla maven project  (there is no black magic going on)- so you can open this up in your favorite IDE.  The generated code uses the MVP pattern that is arguably a best practice for GWT applications.  A lot of people have been struggling with the mechanics of structuring an MVP application (I know I have) – so this should really help.

After the first 10 minutes, I’m really impressed :-)

Installing Oracle 11g on an OpenSolaris zone

April 28, 2010

Here is my cookbook for installing Oracle 11g on an OpenSolaris zone. This has been tested on OpenSolaris build 134, with Oracle 11.2.0.1.0

Pre-requisites:
Create a zone with ( zonemgr or zonadm)
You probably need a machine with at least 2G of RAM

Log in to the zone and run this script (or cut n paste)


#!/bin/sh
# add extra packages needed for Oracle install
pkg install system/install/locale
pkg install system/locale
pkg install system/locale/en_us
pkg install java
pkg install library/motif
pkg install developer/build/make
pkg install developer/object-file

# Add oracle user / groups
groupadd dbagroup
mkdir -p /export/home/oracle
useradd -G dba -m -d /export/home/oracle -s /usr/bin/bash oracle

# Set params for shared memory
# Note - I use dbca to create the database  instance and I set the shared memory to < 1G # If you let the installer create a DB instance you need at least 2G of shared memory 
projadd -U oracle -K "project.max-shm-memory=(priv,1g,deny)" user.oracle projmod -sK "project.max-sem-nsems=(priv,256,deny)" user.oracle 
projmod -sK "project.max-sem-ids=(priv,100,deny)" user.oracle projmod -sK "project.max-shm-ids=(priv,100,deny)" user.oracle 
# Create oracle profile 
cat >/export/home/oracle/.bash_profile  <<EOF
ORACLE_BASE=/opt/oracle
ORACLE_HOME=/opt/oracle/software/102
ORACLE_SID=orcl
LD_LIBRARY_PATH=/lib
PATH=/usr/sbin:/usr/bin:/usr/local/bin:/usr/ccs/bin:/usr/sfw/bin:/bin:$ORACLE_HOME/bin
export ORACLE_BASE ORACLE_HOME ORACLE_SID LD_LIBRARY_PATH PATH
EOF

# Create install dir
mkdir -p /opt/oracle
chown -R oracle /opt/oracle
chown -R oracle /export/home/oracle

When you are done login as oracle, cd to the installer path and run:
./runInstaller

I suggest you defer creating the database at this time.

When the installer is done, run dbca (as user oracle) to set up a DB instance. Edit the shared memory to set it below 1G

After dbca completes em (Enterprise Manager) should be running:

Open your browser to https://yourzone:1158/em

Login as
SYSTEM – Use the password you gave at install time

Reference:

http://wikis.sun.com/display/SAPonSun/Installing+Oracle+11g+Release+2+on+OpenSolaris -
(I found the linking of the amd crypto lib was not required. This may have been fixed)

http://ivan.kartik.sk/oracle/install_ora10gR2_solaris.html

OpenSolaris tip: Switching from IDE to AHCI driver

April 27, 2010

Tip o the day:

I recently upgraded the guts of my home server to a new ASUS Phenom II board.  The boards BIOS defaults to using the IDE emulation for SATA drives -which I realized *after* I installed Solaris. Changing the BIOS to AHCI mode after installation results in a non bootable system as the kernel is looking for the IDE device which is no longer present.

The fix turns out to be simple. Courtesy of uber OpenSolaris guru Jürgen Keil:

1) Boot into the live cd

2) open a shell and run:

pfexec zpool import -f rpool

3) reboot

This fixes up the device path to the new AHCI device.

The OpenSolaris forum thread is here

JDeveloper and MySQL JDBC Drivers

February 26, 2010

As I become “Oracleized”  I am learning JDeveloper. I ran into a snag trying to get the ADF tutorial working with MySQL – and I thought I would pass on the fix.

The embedded Weblogic instance complains that it can’t load the MySQL JDBC driver. This is somewhat confusing – since JDeveloper can find the driver just fine (assuming you add it to your project).  

The trick is to copy the MySQL driver jar  to WebLogic’s lib directory. Finding out which lib directory is not so easy for a newbie -since there are a few possible locations .  This is where Jdeveloper configures your DefaultDomain:

~/.jdeveloper/system11.1.1.2.36.55.36/DefaultDomain/lib

Your install will be slightly different – but you get the gist of it.

Newspeak

February 24, 2010

Newspeak is by far the most interesting programming language being developed today. The mileage they are getting from a few core concepts is very cool.

Gilad’s latest post on object serialization is a great example of how less is more. If you are a programming language geek his blog is well worth checking out.

Google Apps Identity Connector updated with Group support

February 19, 2010

The Google Apps Identity Connector has been updated with a couple of new features:

  • Support for Identity Connector Framework version 1.1
  • Group management. You can now manage user group subscriptions through the connector

This connector allows provisioning of Google Apps accounts using any IdM that supports the connector framework. This is currently limited to Oracle Waveset IdM – but OIM support is on the roadmap!

Please refer to the Google Apps wiki page for more information.

Inline field validation in Scala/Lift using JPA and JSR 303

December 7, 2009

In my experiments with Lift and JPA, there didn’t seem to be a way to display field validation errors next to the field itself (I believe Mapper and Record do this out of the box).  The default approach is to use S.error() to collect up errors and display them all at the bottom of the form. For a large form it is difficult for a user to spot and correct mistakes.  I also wanted to be able to use JSR 303 annotations to perform validation

Lift turns out to be quite flexible, and the Lift community is eager to assist newbies with problems. Alex Siman got me going with a nice tip on generating inline error fields using css. After a bit of hacking (I don’t claim this is pretty) I was able to perform validation using JSR 303.

Generating JPA text fields

Text fields are generated using a jpa helper utility. This is what the binding looks like in a snippet:


bind("user", xhtml,
 "id" -> SHtml.hidden(() => requestVar(current)),
 jpaLabeledTextField("nickName", user, user.nickName, user.nickName = _),
 jpaLabeledTextField("firstName", user, user.firstName, user.firstName = _),
 jpaLabeledTextField("lastName", user, user.lastName, user.lastName = _),
 jpaLabeledTextField("email", user, user.email, user.email = _),
 jpaLabeledTextField("mobilePhone", user, user.mobilePhone, user.mobilePhone = _))

This utility generates a label and an html text field. The label text is looked up using the bean property name. If the bean property is annotated with a @Size attribute the value will be used to set the length of the text field. Here is an example of what a generated form looks like:

Nothing fancy here – but you can see the Mobile phone field is shorter than the name fields. This is because we used a @Size annotation on the mobilePhone field.

Validation

Validations are performed using another utility function. For example:

val valid = jpaIsValid(user)
 if( valid ) {
     val newuser = Model.mergeAndFlush(user)
 .....

The jpaIsValid() function runs JSR 303 validations on the object and returns true if the object is valid.  As a side effect this function will call Lift’s S.error() function and assign validation error messages to the appropriate form fields.  When the form is re-displayed  these error messages will rendered alongside each field.

Here is an example of what this looks like in action:

The Utility Code

And now, without further ado is the utility function. I’m a Scala/Lift newbie – so the style is probably not great (suggestions welcome :-) )

Apologies for the formatting – I don’t know the wordpress tricks to format this nicely. For some reason wordpress also converts some comments to upper case (no, I was not shouting when I wrote this code..)

package com.nextplz.model

/**
* JPAUtil
*/

import net.liftweb.util.{Log, Helpers}

import Helpers._
import net.liftweb.http.{S, SHtml}
import org.scala_tools.javautils.Implicits._

import xml.{Elem, Text, NodeSeq}
import javax.validation.Validation
import javax.validation.constraints.Size
import reflect.Manifest
import collection.mutable.HashMap

/**
* Utilities to generate text fields  using JPA annotations amd to validate those
* fields based on JSR 303 annotations
*
*/
object JPAUtil  {

val validatorFactory = Validation.buildDefaultValidatorFactory();
val validator = validatorFactory.getValidator();

/**
* Generate a text field binding for an object with a JPA String property.
* This will also generate a field label. The label will be looked up using the bean propertyName as a message key
* If the property is annotated with a JSR 303 @Size annotation - the max size will be used to set the
* length of the generated html input text field.
* <br/>
* Example:  bind("user", xhtml, jpaLabeledTextField("firstName", user, user.firstName, user.firstName = _))
* <br/>
* propertyName - must be a valid Java Bean property name for a mapped JPA String field  (e.g. "firstName" )
* obj - the jpa object
* value - the default value to set the input field to
* assign - function that will be called to set the string value of the field
*
* @author Warren Strange
* @author Alex Siman
*
*/
def jpaLabeledTextField[T <: AnyRef](propertyName:String,
obj:T,     // jpa object. In the future we could use reflection to get the value
value:String,
assign:(String) => Any)(implicit m:Manifest[T]):TheBindParam = {

var sz = getMaxSize(m,propertyName)
if( sz == 0 || sz > 150) sz = 20; // todo: what is a sane default for text field size???
TheBindParam(propertyName, formField(propertyName,
SHtml.text(value, assign, "id" -> generateId(obj,propertyName),"size" -> sz.toString()) ))
}

/**
* Run JSR 303 validations on the passed object.
* If there are violations set error message text on the associated text input field
*
*/
def jpaIsValid[T](obj:T):Boolean = {
val violations = validator.validate(obj)
if( violations == null || violations.size() == 0 )
return true

violations.foreach( violation => {
Log.debug("Constraint violation found=" + violation)
val s =  violation.getPropertyPath().toString()
S.error(generateId(obj,s), violation.getMessage() )
})
return false
}

// generate a unique id to identify this input field
private def generateId[T](obj:T, prop:String) = prop + (obj.hashCode.toString())

// Generate a text form field - along with any inline error messages
// Courtesty of Alex Siman
private def formField(label: String, input: Elem): NodeSeq = {
val fixedLabel = label match {
case "" => ""
case s: String => S.?(s) + ":"
}

val id = (input \ "@id").toString

Log.debug("*** Validate id=" + id + " input=" + input + " S.errors=" + S.errors)

val messageList = S.messagesById(id)(S.errors)
//val messageList = S.messagesById(id)(ferrors)
val hasMessages = messageList.size > 0
val cssClass = if (hasMessages) "error" else ""
val messages = messageList match {
case list: List[NodeSeq] if hasMessages => {
<ul>
{messageList.map(m => <li>
{m}
</li>)}
</ul>
}
case _ => Nil
}

<table class={cssClass} style="width: 100%;">
<tr>
<td style="text-align: right; vertical-align: top; width: 10em;">
<b>
{fixedLabel}
</b> &nbsp;
</td>
<td style="text-align: left;">
{input}{messages}
</td>
</tr>
</table>
}

// for caching size values
private val sizeMap = new HashMap[String, Int]
/**
*  If the object is annotated with a @SIZE constraint, grab
* the size to use for the input field length. Return 0 if no max size has been set,
*
*
*/
private def getMaxSize[T](m:Manifest[T], property:String):Int = {
val c = m.erasure
val s = c.getSimpleName() + property
val cached = sizeMap.get(s)
if( cached.isDefined)
return cached.get

val beanDescriptor = validator.getConstraintsForClass(c)
val pd = beanDescriptor.getConstraintsForProperty(property)

Log.debug("propdescriptor=" + pd + " beanDescrip=" + beanDescriptor + "Constraint=" + constraint)
if( constraint.getAnnotation().isInstanceOf[Size]) {
val size = constraint.getAnnotation.asInstanceOf[Size]
sizeMap.put(s, size.max() )
return size.max()
}
})

}
return 0
}

}
Follow

Get every new post delivered to your Inbox.