Thursday, May 17, 2007

Grails + Wicket: The Wonders of the Grails Plug-in system

Update: A shorter Wicket plug-in installation guide has been added here

A few people have been asking me recently to integrate the Wicket project as a view technology for Grails. What is Wicket? It is a component oriented framework kind of like JSF, but unlike JSF it uses convention-over-configuration and doesn't require you to edit reams of XML. So in this sense its aims are more inline with Grails' aims.

So the question was how do we integrate these two frameworks? Well I thought it couldn't be that difficult and as it turns out, it isn't. So what I did was create a plug-in with "grails create-plugin wicket". The next thing I needed were some jars, so I got Wicket 1.2.6, Wicket Extensions 1.2.6 jar and the Wicket Spring integration jars for 1.2.6 and put them in the plug-ins lib directory.



Job done, now we need to set-up a convention that makes sense for both Grails and Wicket:

1) Since controllers are essentially roughly analogous to the Wicket Application object I decided that the convention would be to have a WebApplication.groovy file in the grails-app/controllers directory.
2) Wickets HTML components are like the views, so they can live in the grails-app/views directory

So to follow the HelloWorld example on the Wicket page we end up with something like this being the structure:



So just for clarify the WebApplication.groovy file looks like this:

import wicket.Page;
import wicket.spring.*;

public class WebApplication extends SpringWebApplication
{
public Class getHomePage()
{
return HelloWorld.class;
}
}

Whilst HelloWorld.groovy looks like this:

import wicket.markup.html.WebPage;
import wicket.markup.html.basic.Label;

public class HelloWorld extends WebPage
{
public HelloWorld()
{
add(new Label("message", "Hello World!"))
}
}

Finally, the HelloWorld.html file looks like this:

<html>
<body>
<span wicket:id="message">Message goes here</span>
</body>
</html>

Ok so with that done, let's take advantage of the conventions we have in place here. When I created the plug-in with "grails create-plugin" it also created me a WicketGrailsPlugin.groovy file in the root of the plugin project. Since the normal controller mechanism no longer makes sense we modify the plug-in to "evict" the controllers plug-in when installed:


class WicketGrailsPlugin {
def version = 0.1
def dependsOn = [:]
def evicts = ['controllers'] // evict the controllers plugin
...
}


With that done we now need to configure the Wicket "applicationBean" in Spring. To do this we're going to use the GrailsApplication object and find a class that is an instance of a Wicket Application. This class just happens to be the one we defined earlier in grails-app/controllers:


import wicket.*
class WicketGrailsPlugin {
...
def doWithSpring = {
def applicationClass = application
.allClasses
.find { Application.class.isAssignableFrom(it) }

if(applicationClass) {
applicationBean(applicationClass) // defines the spring bean
}
}
}


We're of course using the Spring BeanBuilder here to define the bean definition if the Spring context. Ok job done.

Now we need to modify web.xml.. but wait. In Grails even web.xml is created on the fly so other plugins can participate in its generation. So we can do this in the plug-in file again:


import wicket.*
class WicketGrailsPlugin {
...
def doWithWebDescriptor = { xml ->
def servlets = xml.servlet[0]

servlets + {
servlet {
'servlet-name'('wicket')
'servlet-class'('wicket.protocol.http.WicketServlet')
'init-param' {
'param-name'('applicationFactoryClassName')
'param-value'('wicket.spring.SpringWebApplicationFactory')
}
'load-on-startup'(1)
}
}

def mappings = xml.'servlet-mapping'[0]
mappings + {
'servlet-mapping' {
'servlet-name'('wicket')
'url-pattern'('/app/*')
}
}
}
}


Here we use Groovy's XmlSlurper DSL to modify and the XML simply by using the + operator and some Groovy mark-up. We again use the Wicket Spring integration support to get it all working, so when Wicket loads it will actually look for the bean created by the Grails plug-in. And that's it, to test the plug-in we can type "grails run-app" and navigate to http://localhost:8080/grails-wicket/app and we see:



Not hugely impressive I know, but what it does show is Wicket running as the view tech in Grails which means that Wicket components and applications can use GORM to simplify the ORM layer of Wicket applications and other features of Grails like Services and Jobs. This took me all of 20 minutes to write, in fact I've spent longer writing this article than the plug-in. Not that it is complete of course, things left to do are to add reloading support to the Groovy files that Wicket users. Advice from the Wicket community on how to do that would be appreciated. And of course I haven't tested it against any more complex examples yet.

Now of course it wouldn't be a plug-in if it couldn't be installed into other apps. To do this we package it up with "grails package-plugin" which will create a zip. I've placed in plug-in at http://dist.codehaus.org/grails-plugins/ so in any Grails application you can now do:

grails install-plugin Wicket 0.1

or directly from the URL:

grails install-plugin http://dist.codehaus.org/grails-plugins/grails-Wicket-0.1.zip

And it will install this version of the plug-in.

Note: The plug-in doesn't work with the Grails URL mapping mechanism in 0.5 so you need to delete any grails-app/conf/*UrlMappings.groovy files otherwise you'll get an exception.

Plug-in sources can be found here: http://svn.grails-plugins.codehaus.org/browse/grails-plugins/grails-wicket/trunk

13 comments:

Erik van Oosten said...

Interesting!

Do I understand that the main advantage of using this, is access to GORM from Wicket? That would be cool because I really like Wicket, but admittedly with envy to dynamic stuff like ActiveRecord. Anyway, if this is true, aren't there more direct ways of using GORM in Wicket? I am unfamiliar with using Groovy so I'am probably missing something obvious.

Erik van Oosten said...

I found answers in http://www.nabble.com/Gricket%3A-The-Love-Child-of-Grails-and-Wicket-tf3772804.html

Thanks Grame.

Alex Kochnev said...

Wow, this is beautiful! I assume that doing something similar to integrate Tapestry as a view plugin for Grails would be very similar... That's my personal "Holy Grail" - be able to use GORM from a tapestry based front end.. Wow !!!

Graeme Rocher said...

Yes it would be similar. There is also someone doing a JSF integration that will be similar to the above I imagine

Alex Kochnev said...

Graeme, not sure if you would care about this, but I started on the Tapestry-Grails plugin, the first (of a series) of blog posts on the subject are at Grails + Tapestry = Grapestry ? Part 1 (of n).

Antony Stubbs said...

Hi Graeme,
Had a look at the wicket plugin, which I'm very excited about the possibilities of, but had a problem.

Is there a bug tracker which is appropriate for the plugin? Or should I simply post the the grails-plugin module on Jira?

The issue (which I also posted on the mailing list here

wicket-grails plugin doesn't look for *.html in grails-app/views

The 0.3 wicket-grails doesn't seem to look for the wicket html files in the views directory. I get the exception wicket.markup.MarkupNotFoundException: Markup not found.

If I put the HelloWorld.html in either the project root, or web-app/WEB-INF/classes/ it finds it there ok.

I'll try and download the plugin from SVN and see if that's any different.

Anonymous said...

Waav! Really nice article. keep writing like this :) Really helps me a lot.!

Raj

SimonLei said...

hi, Antony Stubbs,

I encountered the same problem as you. And I find a dirty but quick way to
solve the problem. I modified the
$GRAILS_HOME/scripts/Package.groovy,
add lines:

fileset(dir:"${basedir}/grails-app/views") {
include(name:"**/**")
exclude(name:"**/*.groovy")
}

at line 146, just below the lines copy files from src/java.

Antony Stubbs said...

@SimonLei:
Ah yes I can see why html files from the view directory would be excluded! I wonder what GR did when he tried it. However, I can imagine people would want to include plain ol' html files into their sites.

I'm not familiar with Grails much, but I'm confident there would be a way for the plugin to hook into the build process and get it's groovy files copied from the views directory.

I shall open an issue for this - so we can track it. One step closer to Wicket integration :)
Issue: http://jira.codehaus.org/browse/GRAILS-2182

I wonder if Wicket DataBinder could still be used along with this, or even Wicket Web Beans. GORM definitely makes searching / listing for beans easy though.

SimonLei said...

Wicket use many inner anonymous class, but groovy doesn't support it. I have to write many unnecessary classes that could be writen as inner anonymous in java class. So I need a smarter way to write wicket pages in groovy way.

I found WicketBuilder at http://wicketstuff.org/confluence/display/STUFFWIKI/wicket-groovy .But it doesn't support wicket 1.3 yet. :( Any suggestion?

Anonymous said...

Great blog with lots of useful information and excellent commentary! Thanks for sharing. DSL internet service providers

Anonymous said...

buy wow gold from top 10 best website.

wow gold discount store,

cheap wow gold  bargain shop;

ffxi gil for you to experience the power.

world of warcraft gold last minute fire sale.

buy ffxi gil with 5% coupon for your character.

everquest platinum market and play to win your game.

warhammer gold at inexpensive price.

cheap ffxi gil to have, why farm the gil yourself.

cheap wow power leveling for your new toons.

cheapest wow gold available with us.

wow gold for sale to celebrate

Anonymous said...

You smart and buy Sword of the New World Vis, you play the game is right, Sword of the New World Gold. you have a wonderful time, buy vis, I have it cheap snw vis, I buy Sword of the New World money.

I find job Tales Of Pirates gold, this is a nice work andTales Of Pirates money. I do not want to rely on my parents cheap Tales Of Pirates gold, They look after me so long time, they pay me too much and very tired buy Tales Of Pirates Gold.