JBoss.orgCommunity Documentation
Errai includes a comprehensive marshalling framework which permits the serialization of domain objects between the browser and the server. From the perspective of GWT, this is a complete replacement for the provided GWT serialization facilities and offers a great deal more flexibility. You can use it with your own application-specific domain models as well as preexisting models, including classes from third-party libraries using configuration files or the custom definitions API.
All classes that you intend to be marshalled between the client and the server must be exposed to the marshalling framework explicitly. There are several ways to do that, and this section will take you through the different approaches.
The easiest way to make a Java class eligible for serialization with Errai Marshalling is to mark it with the org.jboss.errai.common.client.api.annotations.Portable
annotation. This tells the marshalling system to generate marshalling and demarshalling code for the annotated class and all of its nested classes.
The mapping strategy that will be used depends on how much information you provide about your model up-front. If you simply annotate a domain type with @Portable
and do nothing else, the marshalling system will use an exhaustive strategy to determine how to serialize and deserialize instances of that type and its nested types.
The Errai marshalling system works by enumerating all of the Portable types it can find (by any of the three methods discussed in this section of the reference guide), eliminating all the non-portable types it can find (via @NonPortable
annotations and entries in ErraiApp.properties
), then enumerating the marshallable properties that make up each remaining portable entity type. The rules that Errai uses for enumerating the properties of a portable entity type are as follows:
foo
, then that entity has a property called foo
unless the field is marked static
or transient
.Note that the existence of methods called getFoo()
, setFoo()
, or both, does not mean that the entity has a property called foo
. Errai Marshalling always works from fields when discovering properties.
When reading a field foo
, Errai Marshalling will call the method getFoo()
in preference to direct field access if the getFoo()
method exists.
When writing a field foo
, Errai Marshalling gives first preference to a parameter of the mapping constructor (defined below) annotated with @MapsTo("foo")
. Failing this, Errai Marshalling will the method setFoo()
if it exists. As a last resort, Errai Marshalling will use direct field access to write the value of foo
.
Each field is mapped independently according to the above priority rules. Portable classes can rely on a mix of constructor, setter/getter, and field access.
For de-marshalling a @Portable
type, Errai must know how to obtain a new instance of that type. To do this, it selects a mapping constructor (which could literally be a constructor, but could also be a static factory method) using the following rules:
@MapsTo
, then this is the mapping constructor. The constructor doesn’t have to be public.@MapsTo
, then it is the mapping constructor. Unlike a constructor, such a method is free to return an instance of a subtype of the marshalled type, or resolve an instance from a cache. In this case, do keep in mind that left-over properties not covered by the method’s @MapsTo
parameters will still be written by setters and direct field access.If no suitable mapping constructor can be found on a type marked @Portable
, it is a compile-time error.
Now let’s take a look at some common examples of how this works.
@Portable
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
This class is a straightforward mutable bean. Errai will read and write the name and age properties via the setter and getter methods. It will create new instances of the type using the default no-args public constructor that the Java compiler generated implicitly.
It’s always good to aim for truly immutabile value types wherever practical, and Errai’s marshalling system does not force you to compromise on this ideal.
@Portable
public final class Person {
private final String name;
private final int age;
public Person(@MapsTo("name") String name, @MapsTo("age") int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
Both fields are final, so they cannot be written by setter methods or by direct field access. But that’s okay, because we have given Errai a way to set them using the annotated constructor parameters.
Another good practice is to use a factory pattern to enforce invariance. Once again, let’s modify our example.
@Portable
public class Person {
private final String name;
private final int age;
private Person(String name, int age) {
this.name = name;
this.age = age;
}
public static Person createPerson(@MapsTo("name") String name, @MapsTo("age") int age) {
return new Person(name, age);
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
Here we have made our only declared constructor private, and created a static factory method. Notice that we’ve simply used the same @MapsTo
annotation in the same way we did on the constructor from our previous example. The marshaller will see this method and know that it should use it to construct the object.
For types with a large number of optional attributes, a builder is often the best approach.
@Portable
public class Person {
private final String name;
private final int age;
private Person(@MapsTo("name") String name, @MapsTo("age") int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@NonPortable
public static class Builder {
private String name;
private int age;
public Builder name(String name) {
this.name = name;
return this;
}
public Builder age(int age) {
this.age = age;
return this;
}
public Person build() {
return new Person(name, age);
}
}
}
In this example, we have a nested Builder
class that implements the Builder Pattern and calls the private Person
constructor. Hand-written code will always use the builder to create Person
instances, but the @MapsTo
annotations on the private Person
constructor tell Errai Marshalling to bypass the builder and construct instances of Person directly.
One final note: as a nested type of Person
(which is marked @Portable
), the builder itself would normally be portable. However, we do not intend to move instances of Person.Builder
across the network, so we mark Person.Builder
as @NonPortable
.
Some classes may be out of your control, making it impossible to annotate them for auto-discovery by the marshalling framework. For cases such as this, there are two approaches to include these classes in your application.
The first approach is the easiest, but is contingent on whether or not the class is directly exposed to the GWT compiler. That means, the classes must be part of a GWT module and within the GWT client packages. See the GWT documentation on Client-Side Code for information on this.
If you have client-exposed classes that cannot be annotated with the @Portable
annotation, you may manually map these classes so that the marshaller framework will comprehend and produce marshallers for them and their nested types.
To do this, specify them in ErraiApp.properties, using the errai.marshalling.serializableTypes
attribute with a whitespace separated list of classes to make portable.
Example 5.1. Example ErraiApp.properties defining portable classes.
errai.marshalling.serializableTypes=org.foo.client.UserEntity \ org.foo.client.GroupEntity \ org.abcinc.model.client.Profile
If any of the serializable types have nested classes that you wish to make non-portable, you can specify them like this:
Example 5.2. Example ErraiApp.properties defining nonportable classes.
errai.marshalling.nonserializableTypes=org.foo.client.UserEntity$Builder \ org.foo.client.GroupEntity$Builder
The marshalling framework supports and promotes the concept of marshalling by interface contract, where possible. For instance, the framework ships with a marshaller which can marshall data to and from the java.util.List
interface. Instead of having custom marshallers for classes such as ArrayList
and LinkedList
, by default, these implementations are merely aliased to the java.util.List
marshaller.
There are two distinct ways to go about doing this. The most straightforward is to specify which marshaller to alias when declaring your class is @Portable
.
package org.foo.client;
@Portable(aliasOf = java.util.List.class)
public MyListImpl extends ArrayList {
// .. //
}
In the case of this example, the marshaller will not attempt to comprehend your class. Instead, it will merely rely on the java.util.List
marshaller to dematerialize and serialize instances of this type onto the wire.
If for some reason it is not feasible to annotate the class, directly, you may specify the mapping in the ErraiApp.properties file using the errai.marshalling.mappingAliases
attribute.
errai.marshalling.mappingAliases=org.foo.client.MyListImpl->java.util.List \ org.foo.client.MyMapImpl->java.util.Map
The list of classes is whitespace-separated so that it may be split across lines.
The example above shows the equivalent mapping for the MyListImpl
class from the previous example, as well as a mapping of a class to the java.util.Map
marshaller.
The syntax of the mapping is as follows: <class_to_map>
→<contract_to_map_to>
.
When you alias a class to another marshalling contract, extended fields of the aliased class will not be available upon deserialization. For this you must provide custom marshallers for those classes.
Although the default marshalling strategies in Errai Marshalling will suit the vast majority of use cases, there may be situations where it is necessary to manually map your classes into the marshalling framework to teach it how to construct and deconstruct your objects.
This is accomplished by specifying MappingDefinition
classes which inform the framework exactly how to read and write state in the process of constructing and deconstructing objects.
All manual mappings should extend the org.jboss.errai.marshalling.rebind.api.model.MappingDefinition
class. This is base metadata class which contains data on exactly how the marshaller can deconstruct and construct objects.
Consider the following class:
public class MySuperCustomEntity {
private final String mySuperName;
private String mySuperNickname;
public MySuperCustomEntity(String mySuperName) {
this.mySuperName = mySuperName;;
}
public String getMySuperName() {
return this.mySuperName;
}
public void setMySuperNickname(String mySuperNickname) {
this.mySuperNickname = mySuperNickname;
}
public String getMySuperNickname() {
return this.mySuperNickname;
}
}
Let us construct this object like so:
MySuperCustomEntity entity = new MySuperCustomEntity("Coolio");
entity.setSuperNickname("coo");
It is clear that we may rely on this object’s two getter methods to extract the totality of its state. But due to the fact that the mySuperName
field is final, the only way to properly construct this object is to call its only public constructor and pass in the desired value of mySuperName
.
Let us consider how we could go about telling the marshalling framework to pull this off:
@CustomMapping
public MySuperCustomEntityMapping extends MappingDefinition {
public MySuperCustomEntityMapping() {
super(MySuperCustomEntity.class); // (1)
SimpleConstructorMapping cnsMapping = new SimpleConstructorMapping();
cnsMapping.mapParmToIndex("mySuperName", 0, String.class); // (2)
setInstantiationMapping(cnsMapping);
addMemberMapping(new WriteMapping("mySuperNickname", String.class, "setMySuperNickname")); // (3)
addMemberMapping(new ReadMapping("mySuperName", String.class, "getMySuperName")); // (4)
addMemberMapping(new ReadMapping("mySuperNickname", String.class, "getMySuperNickname")); // (5)
}
}
And that’s it. This describes to the marshalling framework how it should go about constructing and deconstructing MySuperCustomEntity
.
Paying attention to our annotating comments, let’s describe what we’ve done here.
MappingDefinition
passing our reference to the class we are mapping.SimpleConstructorMapping
class, we have indicated that a custom constructor will be needed to instantiate this class. We have called the mapParmToIndex
method with three parameters. The first, "mySupername"
describes the class field that we are targeting. The second parameter, the integer 0
indicates the parameter index of the constructor arguments that we’ll be providing the value for the aforementioned field in this case the first and only, and the final parameter String.class
tells the marshalling framework which marshalling contract to use in order to de-marshall the value.WriteMapping
class, we have indicated to the marshaller framework how to write the "mySuperNickname"
field, using the String.class
marshaller, and using the setter method setMySuperNickname
.ReadMapping
class, we have indicated to the marshaller framework how to read the "mySuperName"
field, using the String.class
marshaller, and using the getter method getMySuperName
.ReadMapping
class, we have indicated to the marshaller framework how to read the "mySuperNickname"
field, using the String.class
marshaller, and using the getter method getMySuperNickname
.There is another approach to extending the marshalling functionality that doesn’t involve mapping rules, and that is to implement your own Marshaller
class. This gives you complete control over the parsing and emission of the JSON structure.
The implementation of marshallers is made relatively straight forward by the fact that both the server and the client share the same JSON parsing API.
Consider the included java.util.Date
marshaller that comes built-in to the marshalling framework:
Example 5.3. DataMarshaller.java from the built-in marshallers
@ClientMarshaller(Date.class)
@ServerMarshaller(Date.class)
public class DateMarshaller extends AbstractNullableMarshaller<Date> {
@Override
public Date[] getEmptyArray() {
return new Date[0];
}
@Override
public Date doNotNullDemarshall(final EJValue o, final MarshallingSession ctx) {
if (o.isObject() != null) {
EJValue qualifiedValue = o.isObject().get(SerializationParts.QUALIFIED_VALUE);
if (!qualifiedValue.isNull() && qualifiedValue.isString() != null) {
return new Date(Long.parseLong(qualifiedValue.isString().stringValue()));
}
EJValue numericValue = o.isObject().get(SerializationParts.NUMERIC_VALUE);
if (!numericValue.isNull() && numericValue.isNumber() != null) {
return new Date(new Double(numericValue.isNumber().doubleValue()).longValue());
}
if (!numericValue.isNull() && numericValue.isString() != null) {
return new Date(Long.parseLong(numericValue.isString().stringValue()));
}
}
return null;
}
@Override
public String doNotNullMarshall(final Date o, final MarshallingSession ctx) {
return "{\"" + SerializationParts.ENCODED_TYPE + "\":\"" + Date.class.getName() + "\"," +
"\"" + SerializationParts.OBJECT_ID + "\":\"" + o.hashCode() + "\"," +
"\"" + SerializationParts.QUALIFIED_VALUE + "\":\"" + o.getTime() + "\"}";
}
}
The class is annotated with both @ClientMarshaller
and @ServerMarshaller
indicating that this class should be used for both marshalling on the client and on the server.
The doNotNullDemarshall()
method is responsible for converting the given JSON object (which has already been parsed and verified non-null) into a Java object.
The doNotNullMarshall()
method does roughly the inverse: it converts the given Java object into a String (which must be parseable as a JSON object) for transmission on the wire.