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:
- Annotate: create an annotation,
@CalculatedFieldin my case, and make it available in runtime (RetentionPolicy=RUNTIME). - Decorate: create a decorator,
CalculatedFieldDecorator, which overrides thesetPropertymethod and ignores calls to it if the getter of that property is annotated with@CalculatedField. - Extend: create
MyRequestFactoryServletwhich extends GWT’s servlet, and pass our decorator to it - 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.
Liked Chaotic Java? It's free! But I also make some other things that aren't, which you might like. Like Firewall, a rule changing, turn based strategy game for iOS.