2007-11-30

Groovy Grails GORM and its flaws

NOTE: This problem was fixed in the Grails 1.0 release making GORM the most amazing Object Relational Mapper ever created by mortals.

I am beginning to understand the limitations in Grails better. I would say the "honey moon" phase is drawing to a close now and I can effectively evaluate the tool in light of some of its limitations. One such limitation is GRAILS-1308 which is slated for fix on the Grails 1.1 release... yes the Grails 1.1 release... NOT the up coming Grails 1.0 release. It turns out that this is a significant problem.

The problem in a nutshell is that if you have:

class Company {
Long id
Long version

String name

static hasMany [employees: EmployeeBean]
}

... and that EmployeeBean can be the one from this tutorial or even a straight Groovy domain object... and you try todo this:

def people = EmployeeBean.list()
people.each({ e ->
company.addToEmployees(e)
})
company.save()


The employees will simply not make it into the database... more over GORM won't even bother setting up anything to associate the two classes... ick.

What I end up doing is either:

  • edit EmployeeBean so it "belongsTo" the company

  • create a "bridging" object like "Contract"



I haven't tested going to all JPA classes yet, but I wonder if that wouldn't work.

I had not even noticed that I was working around this problem because I just naturally saw that things weren't working for me the way I wanted to I tried to figure out what the tool could do... and then I'd do that. Ideally you shouldn't have to do that at all. But, this is reality and we have to ship our projects on the platforms in front of us.

Even with its flaws Grails is still much better at many tasks than any other Java related product I have access to. Seeing flaws like this one and the poor support for SOAP clients in Groovy/Grails make me evaluate where I'll use Grails until version 1.1 comes out. The answer for me is Grails will be my front end system and I'll learn how to do Spring-JNDI bridging or Spring-RMI remoting to tie in more mature EJB3 technologies for the complex tasks.

In other words, Grails becomes more View than Controller for me. It becomes a powerful front-controller system for UI and web-applications. That's a good niche to fill... especially if I can learn to tie in a SessionBean from plain old Java land.

Oh yeah, and I'm sorely disappointed in the Searchable plugin too. It simply doesn't work on newer Grails versions. I might use a standalone WAR with very old Grails in it just to get the searchable feature... or I might wait fitfully for that wonderful plugin to deliver on its promises.

EDIT

Looks like the searchable plugin development has stopped dead in its tracks. Oh well. I guess it will be a while.

EDIT 2

I guess we can look forward to this bug being fixed in Grails 1.0! This just shows how amazingly responsive Graeme Rocher and his team are. Think of how many utterly disappointing software releases we've seen in the last few years. The Grails 1.0 release could really count as a breath of fresh air. I know getting this kind of response from a development team surely is for me. It bodes well for the future of Grails.

I can live without a plugin for a while if it means I get a sterling framework for it to plugin to.

2007-11-29

Working Grails and JPA... troubleshooting

I've been working with Grails and the JPA. In eclipse I use the Hibernate3 reverse engineer tool. To make that work I use a hibernate.cfg.xml file that I place in my project's top level folder and a hibernate.reveng.xml file also in the top level folder. I then ask the tool to create EJB3 annotated POJOs and drop them into my grails project.

Previously, I had been deleting the hibernate.cfg.xml and hibernate.reveng.xml files out of the project but yesterday I forgot to do that. What happens if you leave these extra files in your project is that none of the JPA objects get mapped and are therefore invisible to your GORM.

It turns out GORM is reading the top level hibernate.cfg.xml file so why would it see those classes? I never mapped the EJB3 entity beans in the hibernate.cfg.xml I was using for reverse engineer. So, no surprise. There won't be an error either because there were no hibernate errors. The GORM was working perfectly well against the hibernate.cfg.xml in the top level directory.

Now you might wonder why I have two different hibernate.cfg.xml files? Well, the answer is that I need different configurations for the running environment and for the reverse engineer environment. That might be peculiar for me.

Is this a bug? No, not really. It was very hard to diagnose because of the lack of output. Even turning all the logs wide open produced no problems. It wasn't until I went through the project and reverted it to an old version then added one change at a time to it that I noticed the problem was related to the extra hibernate file. At least I know to watch for that now.

2007-11-27

Grails Datasource, legacy database, and you

Any one out there trying to work with Groovy, Grails, and legacy databases?

Well, I am and I thought I'd share with you my final dataSource configuration. I'm using a legacy database that drove an Apache + mod_perl web site that had a MySQL 3.x database. The Groovy/Grails project I'm working on will interact with that data (right now as a read-only service) and here's the final dataSource definition I ended up using for our first beta.


import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsAnnotationConfiguration
/**
* This project is driven off of Legacy data.
* Use update to prevent wiping old tables...
* much of the data is accessed using JPA classes generated by
* the Hibernate3 reverse engineer tool.
********************************************************************/
dataSource {
configClass = GrailsAnnotationConfiguration.class
dbCreate = "update"
dialect= org.hibernate.dialect.MySQLMyISAMDialect // or perhaps MySQL5Dialect
pooled = false
}
environments {
development {
dataSource {
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://localhost:3306/legacy?zeroDateTimeBehavior=convertToNull"
username = "legacy"
password = "legacy"
}
}
test {
dataSource {
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://localhost:3306/legacy?zeroDateTimeBehavior=convertToNull"
username = "legacy"
password = "legacy"
}
}
production {
dataSource {
jndiName = "java:/LegacyDS"
}
}
}


The LAMP stack was upgraded to use MySQL 5 recently so we can set up our new projects using the setting dialect= org.hibernate.dialect.MySQLInnoDBDialect which will give us the ability to use actual foreign key relationships. That's a big deal if you are a LAMP developer. I suppose that in short order I will be a Grails, Linux, Apache, MySQL developer... a GLAM developer... ah... no... lets not call it that.

BTW: I deploy to a JBoss Application server in production which manages the DataSources in a legacy-ds.xml file under /usr/local/jboss/server/default/deploy that's why my production dataSource uses the jndi lookup.

2007-11-26

Alpha Geeks: say w00t!

2 Types of programmers
... what many would call "alpha" programmers — the leaders, trailblazers, trendsetters, the kind of folks that places like Google and Fog Creek software are obsessed with hiring. These folks were the first ones to install Linux at home in the 90's; the people who write lisp compilers and learn Haskell on weekends "just for fun"; they actively participate in open source projects; they're always aware of the latest, coolest new trends in programming and tools.


Yet, we work for and produce software for use by casual programmers and non-programmers. And as Sussman's post points out we had best not forget it. That means you. If you are reading my blog chances are an alpha geek (or just horribly lost).

What's the point of all the pretty shiny objects if we can't get people to use the pretty shiny objects?

2007-11-21

Interoperability is the key to success

I just read the We have lost control post by Reg Braithwaite that's been making the rounds of the blog-o-sphere. Basically, modern IT departments compete directly with the internet. We have to. It's the 21st century. That's because...

Our users are being exposed to applications we don’t control. And it messes things up.


It means that our end users have used the internet and if our programs don't measure up they know it. You can't hide it! They know! Braithwaite tells us this lovely story about working on a CRM for his mum:


For example, my Mother uses Skype to talk to her friends. She thinks it’s normal to see all of your voice mail messages in a list on the screen. If I tried to give her a CRM application for managing contacts, the very first question she would ask would be, “Why can’t I listen to all of the voice mails from that contact in the application?”

Do you think she would have patience for my explanation that the company’s phone systems are complex and proprietary and that we can’t install Asterix just for her? She would grab me by the ear and drag me to my desk to get cracking on it!


In other words if you ship a CRM today that can't interoperate with a phone system... or your phone system can't interoperate with a CRM... your in house CRM will not measure up to the internet based competition... Skype.

And is the user wrong to expect a Skype-like experience? Should the IT department tell them to go suck an egg? I don't think that will fly.

Long story short modern IT departments will have to provide in-house mash-ups of services to be able to provide an end user experience that can compete with the wild and wooly internet.

BTW: I now believe that the Grails Searchable Plugin will change my life. Even though I haven't used it yet. Because I can see how full text search wins and how that plugin can single handedly enable search for all my projects. Yes... the computing world is about to change... again. Hold on for the barrel roll.

2007-11-20

helloLDAP.groovy

Here's a short script that I wrote just to test working with an ActiveDirectory server. It's a short hack of a script that you can use to just test settings and the like. Thanks to this script I know I've found the right settings to work with our Microsoft ActiveDirectory server through Java. I could even use this groovy code to set up a Spring Bean service.


#!/usr/bin/env groovy

import java.util.Hashtable
import javax.naming.*
import javax.naming.event.*
import javax.naming.directory.*

def ou = " System and Service Accounts"

// here, we're just reading in some parameters...
def stdin = new BufferedReader(
new InputStreamReader(System.in)
)
print "username: " // your username to access LDAP... not a real user
String username = stdin.readLine()
print "password: " // the password for that account...
String passwd = stdin.readLine()

// next we set up an environment for LDAP
Hashtable env = new Hashtable()
env.put(
Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory"
)
env.put(
Context.SECURITY_AUTHENTICATION,
"EXTERNAL"
)
/* // if only I had SSL working... *sigh* oh well...
env.put(
Context.SECURITY_PROTOCOL,
"ssl"
) //*/

// You'll want to put your server's name or IP here:
env.put(
Context.PROVIDER_URL,
"ldap://activedirectoryhost.mycompany.com:636/"
)

// Here, you'll want to set up your company's parameters for OU and DC...
env.put(
Context.SECURITY_PRINCIPAL,
"CN=" + username + ",OU=" + ou + ",DC=mycompany,DC=com"
) // username
env.put(
Context.SECURITY_CREDENTIALS,
passwd
) // and password

// Now we'll try and connect...
try {
def ctx = new InitialDirContext(env)
def attr = ctx.getAttributes("")
def srchInfo = new SearchControls()
// you'll want to use your own search base
def searchBase= "OU=company,DC=mycompany,DC=com"
// this is the magic search string sauce:
def searchFilter = "(&(objectClass=person))"
// we strong type the next var because it is
// used in a Java API that needs String[]
String[] objAttribs=[
"givenName",
"cn",
"sn",
"mail",
"mailNickname",
"userPrincipalName",
"displayName",
"manager",
"department",
"memberOf"
]
srchInfo.setSearchScope(SearchControls.SUBTREE_SCOPE)
srchInfo.setReturningAttributes(objAttribs)

// Now we get the results and loop through them...
NamingEnumeration dirObjects = ctx.search(searchBase,searchFilter,srchInfo)
def nodirObjects = 0
while (dirObjects != null && dirObjects.hasMoreElements()) {
def dirObject = dirObjects.next()
println("'" + dirObject.getName() + "':")
def attrs = dirObject.getAttributes()
for(name in objAttribs) {
println "\t * " + attrs.get(name)
}
nodirObjects++
}
ctx.close()
println("Number of entries identified: " + nodirObjects)
} catch (Exception e) {
println "Exception: " + e
}

2007-11-17

Grails Searchable Plugin... wow.

Words fail me Searchable plugin for Grails... wow. I'm speechless... I'm... wow. I just don't know what to say... wow.

Is anybody using this yet? I'm going to have to plug this into my very next project... wow.