Sunday, January 11, 2009

T5 - Scala : first injection and property access

The last time, we set-up the Tapestry 5 quickstart application to run with Scala in place of Java. Now, it's time to add our first service and data, to see what are the common idiom we will have to follow to make them work together.

Target scope of this iteration



We want to add only two features:

  • Two domain objects:


    • the author for our blog,

    • a configuration object, which will be a container for blog's parameters (title, author, etc)


  • a service to access this configuration, injected where needed



After the domain object will be added, I will mmodify the Index page to use them.

User, configuration and service, first attempt



User and configuration objects


So, we want an user with a non-modifiable login, a name/surname, and an email. For now, the configuration will only have the main author (an user), and a title for the blog.
They will go in the same file as they are both related to configuration data:

org.example.blog.data.BlogConfiguration

package org.example.blog.data

class BlogConfiguration(val author : User) {
var blogTitle = "%s's blog".format(author.login)
}

class User(val login : String) {
var commonname = ""
var surname = ""
var email = ""
}


And that's it. For the user, commonname, surname, and email are "var", so we will be able to get them with user.email, and set them with user.email = "newValue".
We can note that the type of this properties is inferred by Scala to String. Remember, Scala is statically typed, but that doesn't imply

login (for user) and author (for configuration) are constructor's parameter. As they are tagged "val", they will be read-only, and viewable from other class.

Configuration service



Now, we have to build a service for configuration so that we will be able to inject it where we need it.

Thank's to Tapestry 5 powerful and super-easy IoC feature, it's a matter of adding some lines in our application module:

org.example.blog.services.AppModule

def buildBlogConfiguration = {
val author = new User("jondoe")
author.commonname = "Jon"
author.surname = "Doe"
author.email = "jondoe@foo.bar"

new BlogConfiguration(author)
}


And it's all what we need. Now, let's use this service in our Index page to display some info about the user !


Index modification



Index.scala


The index page modification is really trivial. That's the code for the Scala part:
org.example.blog.pages.Index

class Index {

@Inject @Property
var conf : BlogConfiguration = _

}


We want to access our BlogConfiguration service, so we just declare a variable with the wanted type. The really important thing here is to initialized it with "_"

Without that (for example, if you initialized the var with null), you will get silly error about read-only variable not modifiable.

As we want T5 to inject the real BlogConfiguration service, we annotated the var with @Inject, and as we want to access it from the template and I'm lazy, we also used the @Property annotation, that save us from writing the getter for "conf".

Index.tml



The template part isn't much more thrilling. We want to use the blog title in the page's title, display information about the author, and even update them. That's the full template code:


<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<head>
<title>Tapestry 5 - Scala blog</title>
</head>
<body>
<h1>${conf.blogTitle}</h1>

<p>Author info:</p>
<t:beandisplay t:object="conf.author" />
<p>Update author info:</p>
<t:beaneditform t:object="conf.author" />
</body>
</html>


"${}" is the T5 syntax to say "look into my class, take the blogTitle object in conf object".
"beandisplay" is a component that simply display all the properties of an object, and "beaneditform" is a magic component that fully build a create/update form for the given object.

And now, we test our blog... And that's not at all what we want.

T5 shows us a really insightful error message:


Clearly, Tapestry does not understand that our "conf" object has a "blogTitle" property.


User, configuration and service, second attempt



What happened is that T5 relies on Java Bean conventions to see what properties of an object are available. And Scala does not follow that convention with the auto-generated accessors. As we see in at the begining, Scala use the directly the property name as getter and setter.

So, we have to add traditional JavaBean getter/setter to our User and BlogConfiguration classes, or use the scala.reflect.BeanProperty where possible. This annotation is the Scala equivalent to T5's Property annotation and generate the getter/setter pair for us :

org.example.blog.data.BlogConfiguration

package org.example.blog.data

import scala.reflect.BeanProperty

class BlogConfiguration(val author:User) {

@BeanProperty
var blogTitle = "%s's blog".format(author.login)

def getAuthor = author
}


class User(val login : String) {
@BeanProperty
var commonname = ""

@BeanProperty
var surname = ""

@BeanProperty
var email = ""

def getLogin = login
}


Sadly, the updated classes look a little more bloated, but now, it works as expected.
If you go to "http://localhost:8080/blog/", you should see :



Of course, if you modify User info and click on "create/update", the user info are really updated in the BlogConfiguration.

And that's over for the time being.

Edit: oups, the code is available here: http://github.com/fanf/scala-t5-blog/tree/article2_20090111

To get the code and run it:

% git clone git://github.com/fanf/scala-t5-blog.git
% cd scala-t5-blog
% git checkout -b test article2_20090111
% mvn jetty:run

And go to http://localhost:8080/blog/

Summary



For now, Tapestry 5 and Scala work together, as soon as you know that "_" is to be used as init placeholder for Injected var, and that T5 use Java Bean convention for getter/setter.

The problem is that we don't really see what Scala brings in place of Java, so why switching ?

We can see it the other way: as everything works at least as well as with Java, I think I will continue this experiment a little bit more : I'm using eclipse with Scala plugin and OK, it's not as good as Java plugin, but it's ok, RunJettyRun has no problem to launch the application, T5 live class reloading works fine, etc.

Moreover, I suspect that as soon as I will come down a little in the application layer, some benefits will emerge - just think to real DAO that take a closure as filter, without having to deal with Java anonymous classes, generic mess, and all that stuff...

2 comments:

Unknown

thanks for sharing your discoveries on this matter.

Fanf

No problem, I'm happy if it can help anyone else ;)

  © Blogger template 'Minimalist G' by Ourblogtemplates.com 2008

Back to TOP