Sep 05

GWT (Google web toolkit) is a great development toolkit for web applications. I’ve been following it for a white now (including two somehwat popular posts about drag and drop and about enums, back when those weren’t so popular..) and it keeps getting better and better. Recently, in version 2.1, they’ve added something called the RequestFactory, which is something nifty to replace GWT-RPC calls when those are needed for DTOs. In effect, it allows you to have most of your logic in the browser, and close to no code in the server side, especially if your application is mostly retrieving data and setting it nicely for display.

This framework does everything for you: serialization, proxy creation, remote call exposure, error handling, and more. And it even gets better in 2.4. But with all that automatic glory, something was missed, which is asymmetric accessors, i.e. writing a getter method without an accompanying setter method. And that’s what I’m writing about today.

Why would I want asymmetric accessors?

So why would we even want asymmetric accessors? Well, the answers lies in the concept of encapsulation, and calculated fields. Just like not all fields in an object are persistent (marked with @Transient), not all fields on the client side are “concrete” on the server side; some of them are calculated from different values. The easiest example for this would be the following:

class Person {
	@NotNull private String firstName;
	@NotNull private String lastName;
 
	@Transient
	public String getFullname() {
		return firstName + " " + lastName;
	}
}

As you can see, the field “fullname” is just a concatination of the first and last names. The implementation hides the contract of the object – perfect encapsulation. However, there cannot be a “setter” method for this field; while we could think of a simple implementation that splits the full name at the first whitespace, it’s not the case for a lot of calculated fields.

ServiceLayer and ServiceLayerDecorator

So who causes the problem in the long chain of the RequestFactory architecture? Essentially, you can find it in a few locations. First, somehow the AutoBean representing your proxy decides that it is different from what’s currently on the server. This happens in very few situations, but when it does, the second part comes in: it tells the ServiceLayer that these properties should be set for the server-side object. The ServiceLayer looks for the setter method, and when failing to find one, throws an exception and – boom, your application fails. I’m skipping a few mediation objects here; maybe one day I’ll write a full article on what happens behind the scenes there.

Luckily, the ServiceLayer is extensible and designed using the chain of responsibility design pattern. You are allowed to write plugins, or ServiceLayerDecorators, and these will work before GWT’s built-in code steps in. This way, for certain funcionality required, you can override GWT’s behavior. But if you’d look through Google’s documents about RequestFactory you won’t find how to use these decorators; but it’s not a tough task. All you need to do is extend the RequestFactoryServlet, and pass the decorators to the parent class when initiaised.

The solution

So the cleanest solution I’ve found so far is:

  1. Annotate: create an annotation, @CalculatedField in my case, and make it available in runtime (RetentionPolicy=RUNTIME).
  2. Decorate: create a decorator, CalculatedFieldDecorator, which overrides the setProperty method and ignores calls to it if the getter of that property is annotated with @CalculatedField.
  3. Extend: create MyRequestFactoryServlet which extends GWT’s servlet, and pass our decorator to it
  4. Wire: use our servlet in the web.xml file, and annotate our field in the class code.

The code should look something like this:

class Person {
	...
	@Transient @CalculatedField
	public String getFullname() { ... }
}
 
class CalculatedFieldDecorator extends ServiceLayerDecorator {
    public void setProperty(Object domainObject, String property, Class expectedType, Object value) {
        if (getGetter(domainObject.getClass(), property).isAnnotationPresent(CalculatedField.class)) {
            // do nothing (it's okay - the value is calculated anyway.)
        } else {
            super.setProperty(domainObject, property, expectedType, value);
        }
    }
}
 
class MyRequestFactoryServlet extends RequestFactoryServlet {
	public MyRequestFactoryServlet() {
		super(new DefaultExceptionHandler(), new CalculatedFieldDecorator());
	}
}

and there you have it! You will never see the message “Could not locate setter for property” ever again!

Conlcusion

I think that either Google overlooked an important issue, or I’m missing something important here. After tweaking my code a lot, I couldn’t get around creating the proposed code, but I’m sure Google could and should make a more elegant solution. On the other hand, I think this is a perfect case for making an API that makes it easy to do most tasks people would want, and makes it possbile to do everything else. Personally, I believe this should be a guideline all API writers should follow, and I’m glad Google chose it here.

Share
Apr 21

For those of you who don’t know what OpenID is, get to know it now. This post is going to be about how to use OpenID authentication in your GWT applications.

First, a reminder of what OpenID is:

OpenID is a free and easy way to use a single digital identity across the Internet.

Continue reading »

Share
Mar 29

For a couple of weeks now, the Google Web Toolkit 1.5 milestone 1 was available for download (announcement here). This completes the set of Java language features added to Java 5 by adding support for enums (much better than my workaround enum), generics and for-each loops. GWT itself even uses generics for the asynchronous calls, so that the AsyncCallback interface now accepts a type parameter which is used later for the onSuccess callback method.

I think that the beautiful part of it all is that for those of us who are already used to the new syntax features, this new release makes GWT development feel even smoother than before.

A word of warning though: from my experience it seems that the milestone doesn’t work well under Mac OS X with the new developers’ preview of Java 6. If you are in this category, you’ll have to rollback your version to the supplied Java 5 implementation.

Share