Errai: The browser as a platform

Friday, March 2, 2012

Marshalling in Errai 2.0: Dealing with the Edge Case

Dovetailing off the last post on marshalling, lovingly laid out by Jonathan, I'd like to talk about dealing with edge cases in Errai 2.0's marshalling framework.

The real question is: what do you do when the annotations are not enough? What if I have to marshal a legacy class that isn't very bean-like.

Well, it turns out, we ran into these problems ourselves when implementing the default JRE marshallers. There are some classes which don't exactly lend themselves to just being slurped up into a reflection analyzer and having marshalling code emitted. And even if they did, they'd create very inefficient marshallers.

There are a grand total of two ways to deal with this problem when you come to it. The first solution is to try and manually describe the model to Errai. The second solution is just to write your own marshaller by hand. Let's look at the former approach today!

Teaching Errai about Your Model

This approach has the advantage of not requiring that you manually deal with building up and parsing wire protocol stuff. Instead, we give Errai a picture of what it should do to build the object through a series of mapping instructions.

A perfect example is the java.lang.Throwable class. Marshalling this class provides a few special challenges. For instance, to construct it with a message we must do so in a constructor argument. To chain a cause to it, we must call initCause(), and so on. Thus, Errai Marshalling's class analyzer is not going to do a very good job at comprehending the class. Instead, we will implement a MappingDefinition to describe the mappings needed to build and deconstruct a Throwable.

The first thing we do is create a class which extends org.jboss.errai.marshalling.rebind.api.model.MappingDefinition. This is a base class which contains the model of the mapping.

@CustomMapping
public class ThrowableDefinition extends MappingDefinition {
  public ThrowableDefinition() {
    super(Throwable.class);

    SimpleConstructorMapping constructorMapping = new SimpleConstructorMapping();
    constructorMapping.mapParmToIndex("message", 0, String.class);
    setInstantiationMapping(constructorMapping);
    addMemberMapping(new WriteMapping("cause", Throwable.class, "initCause"));

    addMemberMapping(new AccessorMapping("stackTrace", StackTraceElement[].class, "setStackTrace", "getStackTrace"));

    addMemberMapping(new ReadMapping("message", String.class, "getMessage"));
    addMemberMapping(new ReadMapping("cause", Throwable.class, "getCause"));
  }
}

Let's go through this line-by-line to see what's going on here.

super(Throwable.class);

We call the super constructor, passing the the class reference for Throwable. This configures the MappingDefinition to associate your mappings with this class.

SimpleConstructorMapping constructorMapping = new SimpleConstructorMapping();

We instantiate a org.jboss.errai.marshalling.rebind.api.model.impl.SimpleConstructorMapping to represent the constructor that will need to be called to instantiate the Throwable.

constructorMapping.mapParmToIndex("message", 0, String.class);

We map the message field of the class to the first argument (index: 0) of the constructor, and specify that is of type java.lang.String.

setInstantiationMapping(constructorMapping);

We call MappingDefinition.setInstantiationMapping() (it's in the super class), to record that this constructor mapping is needed to instantiate the class.

addMemberMapping(new WriteMapping("cause", Throwable.class, "initCause"));

We add a member mapping for the cause field of Throwable. In this case we specify a WriteMapping, which implies a method which accepts exactly one argument. In this case, another Throwable. The method we want is initCause.

addMemberMapping(new AccessorMapping("stackTrace", StackTraceElement[].class, "setStackTrace", "getStackTrace"));

For the stackTrace field, we utilize the AccessorMapping, which is a convenient two-way mapping where we have symmetric getters and setters. In this case, setStackTrace() and getStackTrace().

addMemberMapping(new ReadMapping("message", String.class, "getMessage"));

This time we specify a ReadMapping. Which like its namesake, tells the marshalling framework how to read a field. In this particular case, we map the message field, to the getMessage method. The ReadMapping expects the method to return the specific type (in this case String), and accept exactly zero arguments.

addMemberMapping(new ReadMapping("cause", Throwable.class, "getCause"));

Like with message, we do the same for cause. And guess what? We've told Errai everything it needs to know about marshalling this class. In fact, since many other classes extend Throwable and have the exact same marshalling characteristics, we can place an additional annotation on our class to make this definition project across these types as well!

@InheritedMappings(IOException.class, IllegalArgumentException.class, /* and more...  */))

The use of the InheritedMappings annotation accomplishes the aliasing for us. And that's that! We've described to Errai how to marshal and demarshal Throwable class, and classes which inherit it!

The next in this series will look at writing your own custom marshaller from scratch.

Mike, out.

No comments:

Post a Comment