Errai: The browser as a platform

Wednesday, February 22, 2012

Errai Marshalling: Good for Immutable Types

There are already dozens of well-known serialization frameworks for Java, but Errai needs one that works with code that's been translated to JavaScript via the GWT compiler. For serialization frameworks, GWT presents special challenges: reflection only goes as deep as class literals and Object.getClass(). No dynamic method calls; no dynamic field reads and writes; no dynamic creation of arbitrary types of objects.

So we decided the best way forward for Errai was to build our own annotation-driven marshalling framework based on code generation. My favourite part about this is that we got to design it from the ground up to support immutable types.

First, here's a basic mutable Bean that can be serialized and deserialized by Errai Marshalling:

import java.util.Date;
import org.jboss.errai.common.client.api.annotations.Portable;

@Portable
public class OrientationSensorReading {

  private Date observationTime;
  private double x;
  private double y;
  private double z;

  public Date getObservationTime() { return observationTime; }
  public void setObservationTime(Date observationTime) {
    this.observationTime = observationTime;
  }
  
  public double getX() { return x; }
  public void setX(double x) { this.x = x; }

  public double getY() { return y; }
  public void setY(double y) { this.y = y; }
  
  public double getZ() { return z; }
  public void setZ(double z) { this.z = z; }
}

Pretty easy, right? Just annotate it with @Portable, and OrientationSensorReading objects will be marshallable on both the client and the server. Notice that the java.util.Date instance didn't require any special treatment. In fact, all GWT-translatable types in the standard Java library (including Exceptions) are handled by Errai Marshalling out-of-the-box. Likewise, you can nest instances and collections of any Portable entity types you have declared in your project's codebase.

But wait! This type is a natural candidate for immutability. Once we've taken a sample from an accelerometer, this reading should never change. Here's an immutable variant of the above that works with Errai Marshalling:

import java.util.Date;
import org.jboss.errai.common.client.api.annotations.Portable;
import org.jboss.errai.marshalling.client.api.annotations.MapsTo;

@Portable
public final class OrientationSensorReading {

  private final Date observationTime;
  private final double x;
  private final double y;
  private final double z;

  public OrientationSensorReading(
      @MapsTo("observationTime") Date observationTime,
      @MapsTo("x") double x,
      @MapsTo("y") double y,
      @MapsTo("z") double z) {
    this.observationTime = new Date(observationTime.getTime());
    this.x = x;
    this.y = y;
    this.z = z;
  }

  public Date getObservationTime() { return new Date(observationTime.getTime()); }
  public double getX() { return x; }
  public double getY() { return y; }
  public double getZ() { return z; }
}

Note the @MapsTo annotations on the constructor arguments. These specify the names of the Bean properties the arguments map to. This is necessary because–unfortunately–the names of method and constructor parameters are not preserved in Java class files. I believe the advantage of having truly immutable objects where they make sense in your code will more than make up for this inconvenience.

Errai marshalling also understands the factory method pattern recommended by Josh Bloch in Effective Java. This is also an Errai Marshalling compatible type:


import java.util.Date;
import org.jboss.errai.common.client.api.annotations.Portable;
import org.jboss.errai.marshalling.client.api.annotations.MapsTo;

@Portable
public class OrientationSensorReading {

  private final Date observationTime;
  private final double x;
  private final double y;
  private final double z;

  private OrientationSensorReading(
      Date observationTime, double x, double y, double z) {
    this.observationTimenew Date(observationTime.getTime());
    this.x = x;
    this.y = y;
    this.z = z;
  }

  public static OrientationSensorReading newInstance(
      @MapsTo("observationTime") Date observationTime,
      @MapsTo("x") double x,
      @MapsTo("y") double y,
      @MapsTo("z") double z) {
    return new OrientationSensorReading(observationTime, x, y, z);
  }

  public Date getObservationTime() { return new Date(observationTime.getTime()); }
  public double getX() { return x; }
  public double getY() { return y; }
  public double getZ() { return z; }
}

In this case, because Errai Marshalling simply calls your static method when it needs a particular instance of a type, you can return whatever you want. For example, you can build in an instance cache and guarantee there are no duplicates floating around with the same values. Or you could obtain instances via GWT.create() and take advantage of code splitting in your marshallable types. This kind of flexibility is wonderful.

But what about JAXB? Errai brings Java EE 6 to the browser, and JAXB is part of Java EE 6, right? Well, we would like to implement JAXB on the client eventually, but Errai Marshalling was much quicker to implement and it offers a more compact API that's well-suited to the immediate needs of Errai:
  • JAXB is a much larger API than Errai Marshalling needs. Much of the complexity of JAXB lies in the ability to control the XML representation of the object graph. This is essential if you are mapping Java objects to a predefined XML schema, but a lot of dead weight if you aren't.
  • JAXB does not support marshalling instances of immutable types; Errai Marshalling does.
  • JAXB is primarily intended for XML; Errai Marshalling is primarily intended for JSON.
Note also that Errai Marshalling and JAXB annotations can happily coexist. It's not an either/or choice. In most cases, it suffices to stick "@Portable" on an existing JAXB entity class, and you get the best of both worlds.

8 comments:

  1. Very cool. But technically, the OrientationSensorReading class is not immutable because its getObservationTime() method leaks the java.util.Date object, which is mutable.

    ReplyDelete
    Replies
    1. Thanks, Randall. What an amateur move on my part. :-)

      I've added defensive copies of the dates in the two code examples. Of course at this point, it would be better just to store the dates as longs, but the whole point of putting them there in the first place was to illustrate that marshalling works for built-in library classes.

      Delete
  2. Great to see this blog get some more frequent posts!

    In my mind Errai is technology that should be shipping as part of GWT core. Too bad Google seems so focused on keeping GWT server-side agnostic. The real value in using GWT over other client-side technologies is only fully exploited when you can re-use server-side code and remove boilerplate related to copying values from your server-side RPC calls (whether that's implemented using REST/EJB/etc). Love Errai's approach to this so far. I hope Errai becomes strategic for RedHat and starts geting more marketing so it doesn't just fade away into obscurity. So far Errai seems to be very much under the radar even among people using GWT which is currently fading off the radar due to Google's push towards DART and loss of key people.

    Keep up the good work!

    Corey

    ReplyDelete
    Replies
    1. Trust me when I say we're just getting started! In the past six months, Red Hat has really invested in ramping up Errai. Expect to keep seeing more here.

      Delete
  3. I'm kinda confused by this, doesn't RestEASY got that all figured out? I mean, you can used JAXB annotated POJOs and it does't the JSON part for you, no need to reimplement it from scratch

    ReplyDelete
  4. Hi Solerman,

    RESTEasy does have this figured out. You could use RESTEasy with Errai Marshalling rather than JAXB even if you're not using Errai on the client side.

    In fact, the Errai's JAX-RS module does use RESTEasy on the server side. Errai JAX-RS simply provides a typesafe client-side interface to your RESTEasy resources. In this setup, RESTEasy uses Errai Marshalling rather than JAXB.

    As I said in my posting, we will probably implement JAXB sooner or later. We chose to create Errai Marshalling first for three main reasons: first, JAXB is much more complicated, spending a lot of its complexity to solve a problem we don't have; second, JAXB doesn't support marshalling immutable objects; third, JAXB is primarily intended for XML (yes, you can translate the XML to JSON with something like Jettison–but with Errai Marshalling you don't have to.)

    ReplyDelete
    Replies
    1. Sorry, my first sentence in that reply should have been: "RESTEasy does have this figured out, but if you use JAXB then you have to live with the limitations of JAXB."

      Delete
  5. UPDATE: Mike Pettypiece just mentioned to me that the article as originally published was using @MapsTo("x") for all three axes. I've updated the code examples so they're not wrong. :-)

    Thanks, Mike!

    ReplyDelete