graniteds.orgCommunity Documentation

Chapter 4. Remoting and serialization

4.1. Using the Tide API
4.1.1. Basic remoting
4.1.2. Basic remoting with dependency injection
4.1.3. Using the TideResponder Interface
4.1.4. Simplifying asynchronous interactions
4.1.5. Global exception handling
4.2. Mapping between client and server Java objects
4.3. Externalizers and Java code generation
4.3.1. Example of a JPA entity and its corresponding JavaFX bean
4.3.2. Standard configuration
4.3.3. Autoscan configuration
4.3.4. Built-in externalizers
4.3.5. Built-in client externalizers
4.3.6. Custom externalizers
4.3.7. @ExternalizedBean and @ExternalizedProperty
4.3.8. Custom class getters
4.3.9. Instantiators
4.4. JPA and lazy initialization
4.4.1. Single-valued associations (proxied or weaved associations)
4.4.2. Collections (List, Set, Bag, Map)
4.5. Securing remote destinations
4.5.1. Configuration
4.5.2. Fine-grained per-destination security
4.5.3. Deserialization protection

Data serialization between a client application and a Java EE server may use different kinds of transfer encodings, including XML, JSON, Java serialization, or various other serialization frameworks. GraniteDS provides an implementation of the Adobe AMF3 (ActionScript Message Format) binary encoding which is very compact, fast and efficient. Other formats may be added later but AMF3 is a really easy-to-use and performant format.

The AMF3 format allows for serialization of strongly typed objects. GraniteDS adds the concept of externalization to transform the serialized objects before and after they are serialized. This allows for example to serialize JPA entities without triggering initialization of all lazy properties.

When building a JavaFX client, you can then easily deserialize these entities to a properly JavaFX-bindable bean having the same properties. This way the client and server parts of the application are cleanly separated, the JavaFX bean does not have any dependency (even internal runtime) on the JPA provider and the JPA entity having no dependency on the JavaFX binding API.

The AMF3 format

AMF3 is a very compact binary format for data serialization/deserialization and remote method invocation. A key feature of this format is that it preserves the entire graph of your data without duplicating identical objects (contrary to JSON for example). For example, if A1 and A2 contain a reference to the same B1, the serialization of A1 and A2 does not duplicate B1. The target client VM will contain exactly the same data graph with only one B1 referenced by one A1 and one A2. Furthermore, there is no risk of infinite recursion if the data graph contains circular references. For example, if B1 contains the set of A# that references B1. AMF3 messages are sent as a part of a AMF0 envelope and body. GraniteDS implements an AMF3 serializer/deserializer and relies on some code borrowed from the OpenAMF project for AMF0 serialization/deserialization. The AMF0 and AMF3 specifications are now public. You may download them here. You will need a Macromedia or Adobe account.

The Tide remoting API is an alternative to the low-level RemoteService API that simplifies the handling of asynchronicity and brings much more features that will be described in the next chapters.

Let's see the same hello example with Tide. Note the usage of the Tide context object which reprensents the client application container.



public class HelloExample {
    public static void main(String[] args) {
    
        ContextManager contextManager = new SimpleContextManager(new DefaultPlatform());
        Context context = contextManager.getContext();
        
        ServerSession serverSession = new ServerSession("spring", "/myapp", "localhost", 8080);
        context.set(serverSession);
        serverSession.start();
        
        Component helloService = new ComponentImpl(serverSession);
        context.set("helloService", helloService);
        
        // Asynchronous call using handlers
        helloService.call("sayHello", "Barack", new TideResponder<String>() {
            @Override
            public void result(TideResultEvent<String> result) {
                System.out.println("Async result: " + result.getResult());
            }
            
            @Override
            public void fault(TideFaultEvent fault) {
                System.err.println("Fault: " + fault.getFault());
            }
        };
        
        // Synchronous wait of Future result
        Future<String> futureResult = helloService.call("sayHello", "Barack");
        String result = futureResult.get();
        System.out.println("Sync result: " + result);
    }
            

This is a bit different than the RemoteService API. It looks like a mostly cosmetic changes, but there are many internal things that differ.

The core of the Tide framework is the context which contains the various elements of the application. Here we create a simple ContextManager which implements a very minimalistic built-in application container. For more demanding environment, we recommend using the SpringContextManager which will use a Spring application container.

The Platform SPI is a simple interface that allows to integrate the Tide context with the client UI framework. For example, JavaFX requires that all graphic operations are executed in the main UI thread. The JavaFX platform implementation will ensure that the asynchronous result handlers of remote calls will be executed in the UI thread so you can do whatever UI operation you need using the received data.

The ServerSession encapsulates all communication between the client application and the remote services for a particular server endpoint. Note that here it has to be "attached" manually to the Tide context with context.set(). In a Spring environment, it would just have to be declared as a Spring bean.

Finally the Component instance represents a client proxy to the actual remote service. The method call executes the remote call and returns a Future object which can be used to get the result. It is also possible to provide a last argument to the method call which can implement TideResponder and result, fault. Here we use an untyped ComponentImpl implementation but it's also possible to generate typesafe client proxies from the service interfaces.

The previous example was a bit simplistic, and in more realistic applications you might want to use the client proxies from some controller class instead of the main application (!). For a more 'enterprisy' usage, we might configure a Spring container on the client application.



package com.myapp.client;
@Configuration
public class Config {
    
    @Bean
    public SpringEventBus eventBus() {
        return new SpringEventBus();
    }
    
    @Bean
    public SpringContextManager contextManager(SpringEventBus eventBus) {
        return new SpringContextManager(new JavaFXPlatform(eventBus));
    }
    
    @Bean(initMethod="start", destroyMethod="stop")
    public ServerSession serverSession() throws Exception {
        return new ServerSession("spring", "/test", "localhost", 8080);
    }
    
    @Bean
    public Component helloService(ServerSession serverSession) {
        return new ComponentImpl(serverSession);
    }
    
    @Bean(initMethod="start")
    public App app() {
        return new App();
    }
}
            


package com.myapp.client;
public class App {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.scan("com.myapp.client");
        applicationContext.refresh();
        applicationContext.registerShutdownHook();
        applicationContext.start();
    }
    
    @Inject @Qualifier("helloService")
    private Component helloService;
    
    public void start() {
        helloService.call("sayHello", "Barack", new TideResponder<String>() {
            @Override
            public void result(TideResultEvent<String> result) {
                System.out.println("Async result: " + result.getResult());
            }
            
            @Override
            public void fault(TideFaultEvent fault) {
                System.err.println("Fault: " + fault.getFault());
            }
        };
    }
}
            

Here we use the Spring 3.1 Java configuration mechanism, but you could also do all this in XML or any other Spring configuration style. The important things here are that we declared two components of types EventBus and ContextManager, the ServerSession and a Component as Spring beans. Once everything is properly wired together, you can simply inject the client proxies in whatever bean you want to execute the remote calls.

The TideMergeResponder interface is an extension of TideResponder that makes possible to provide a return object that will be merged with the server result. It helps working with the asynchronous nature of remoting by limiting the need for result handlers.



private List<Product> products = new ArrayList<Product>();
public function call():void {
    productService.findAllProducts(new TideMergeResponder<List<Product>>() {
        @Override
        public void result(TideResultEvent<List<Product>> event) {
            System.out.println("Result was merged: " + (event.getResult() == products));
        }
        
        @Override
        public void fault(TideFaultEvent event) {
            System.err.println("Fault for " + token + ": " + event.getFault());
        }
        
        @Override
        public List<Product> getMergeResultWith() {
            return products;
        }
    });
}
            

This may not seem very useful in this case, but when combined with a data binding mechanism such as the one in JavaFX, that means that you don't have to handle the actual result. By using a JavaFX ObservableList The binding would transparently propagate all incoming remote data to the UI. Note that this kind of merge will work correcly only with mutable objects (so no String, Number, ...). It is generally the most useful with collections.

The server exceptions can be handled on the client-side by defining a fault callback on each remote call. It works fine on a case by case basis but it is very tedious and you can always forget a case, in which case the error will be either ignored or result in a global error popup that is not very elegant.

To help dealing with server exceptions, it is possible to define common handlers for particular fault codes on the client-side, and exception converters on the server-side, to convert server exceptions to common fault codes.

On the server, you have to define an ExceptionConverter class. For example we could write a converter to handle the JPA EntityNotFoundException (in fact there is already a built-in converter for all JPA exceptions):



public class EntityNotFoundExceptionConverter implements ExceptionConverter {
    public static final String ENTITY_NOT_FOUND = "Persistence.EntityNotFound";
    
    public boolean accepts(Throwable t, Throwable finalException) {
        return t.getClass().equals(javax.persistence.EntityNotFoundException.class);
    }
    public ServiceException convert(
        Throwable t, String detail, Map<String, Object> extendedData) {
        ServiceException se = new ServiceException(
            ENTITY_NOT_FOUND, t.getMessage(), detail, t
        );
        se.getExtendedData().putAll(extendedData);
        return se;
    }
}
            

This class will intercept all EntityNotFound exceptions on the server-side, and convert it to a proper ENTITY_NOT_FOUND fault event.

The argument finalException contains the deepest throwable in the error and can be used to check if some higher level exception converter should be used to handle the exception. For example, the HibernateExceptionConverter checks if the exception is wrapped in a PersistenceException, in which case it lets the JPA PersistenceExceptionConverter accept the exception.

This exception converter has to be declared on the GDS server config :

On the client side, you then have to define an exception handler class:



public class EntityNotFoundExceptionHandler implements ExceptionHandler {
    public boolean accepts(FaultMessage emsg) {
        return "Persistence.EntityNotFound".equals(emsg.getCode());
    }
    public void handle(Context context, FaultMessage emsg, TideFaultEvent faultEvent) {
        System.err.println("Entity not found: " + emsg.getMessage());
    }
}
            

... and register it as an exception handler in the Tide context. That is simply declare it as a managed bean with context.set(new EntityNotFoundExceptionHandler()) or as a Spring bean when using Spring.

The server data objects are usually defined as JPA entities. Using them directly on the client is possible but requires having a runtime dependency on the JPA provider on the client, which may not be practical or suitable at all. This is for example what would happen by using standard Java serialization. Additionally, using a JPA entity on a JavaFX client (for example) means that your data beans will not benefit from all the data binding machinery of JavaFX which requires the use of special properties implementations (javafx.beans.property.Property). You could probably build a 'dual' Java class which is both a JPA entity and a bindable JavaFX bean but that would imply a very tight coupling between the client and the server (and a dependency of the server application on JavaFX !!) and might at last not work at all (in particular for collection properties).

Having two different classes for the same data object on the client and the server is thus a cleaner approach and simply requires some tooling to automatically generate one from the other. GraniteDS provides a JPA/JavaBean to JavaFX class generator which handles exactly this task.

Let's say we have a basic entity bean that represents a person. The following code shows its implementation using JPA annotations:



package com.myapp.entity;
import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Version;
@Entity
public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id @GeneratedValue
    private Integer id;
    @Version
    private Integer version;
    @Basic
    private String firstName;
    @Basic
    private String lastName;
    public Integer getId() {
        return id;
    }
    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}
            

With GraniteDS automated externalization and without any modification made to our bean, we may serialize all properties of the Person JPA entity, and convert them to a Person JavaFX bean. Furthermore, thanks to the Gfx code generator, we do not even have to write the JavaFX bean by ourselves. Here is a sample generated bean implementation:



@JavaFXObject
public class PersonBase implements Identifiable, Lazyable, DataNotifier {
    private boolean __initialized = true;
    @SuppressWarnings("unused")
    private String __detachedState = null;
    private final BooleanProperty __dirty = new SimpleBooleanProperty(this, "dirty", false);
    
    private EventHandlerManager __handlerManager = new EventHandlerManager(this); 
    @Override
    public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) {
        return tail.prepend(__handlerManager);
    }
    
    public <extends Event> void addEventHandler(EventType<T> type, EventHandler<? super T> handler) {
        __handlerManager.addEventHandler(type, handler);
    }
    public <extends Event> void removeEventHandler(EventType<T> type, EventHandler<? super T> handler) {
        __handlerManager.removeEventHandler(type, handler);
    }
    
    
    public boolean isInitialized() {
        return __initialized;
    }
    
    @IgnoredMethod
    public BooleanProperty dirtyProperty() {
        return __dirty;
    }
    
    public boolean isDirty() {
        return __dirty.get();
    }
    private ObjectProperty<Long> id = new SimpleObjectProperty<Long>(this, "id");
    private StringProperty uid = new SimpleStringProperty(this, "uid");
    private ObjectProperty<Integer> version = new SimpleObjectProperty<Integer>(this, "version");
    private StringProperty firstName = new SimpleStringProperty(this, "firstName");
    private StringProperty lastName = new SimpleStringProperty(this, "lastName");
    
    public ObjectProperty<Long> idProperty() {
        return id;
    }
    @Id
    public Long getId() {
        return id.get();
    }
    
        
    public StringProperty uidProperty() {
        return uid;
    }
    public void setUid(String value) {
        uid.set(value);
    }
    public String getUid() {
        return uid.get();
    }
    
    public ObjectProperty<Integer> versionProperty() {
        return version;
    }
    @Version
    public Integer getVersion() {
        return version.get();
    }
    
    public StringProperty firstNameProperty() {
        return firstName;
    }
    public void setFirstName(String value) {
        firstName.set(value);
    }
    public String getFirstName() {
        return firstName();
    }
    
    public StringProperty lastNameProperty() {
        return lastName;
    }
    public void setLastName(String value) {
        lastName.set(value);
    }
    public String getLastName() {
        return lastName.get();
    }
}
            

This JavaFX bean reproduces all properties found in the JPA entity, public and private and even includes some extra properties and features, (__initialized and __detachedState), that correspond the the JPA internal state for lazy loading. Note that these two fields are present because the Gfx generator has detected that our class is a JPA entity annotated with @Entity. For simple Java beans, these two fields would not be present, but this shows that the pluggable externalizer mechanism in GraniteDS allows to do a lot more than simply serializing public data and value objects.

You may also notice a few more additions in the generated bean that are useful with more advanced features of the framework. DataNotifier is a interface for bean that can dispatch events related to their internal state, that is used by the form validation framework. dirtyProperty is a bindable property updated by the data management framework that indicates whether the bean has been modified since its last server update.

With the externalizer mechanism in GraniteDS, serializing data between a client and a server is almost as powerful as pure Java serialization and additionally allows to maintain a clean decoupling between the client and server applications, whatever framework is used on both sides.

In order to externalize the Person.java entity bean, we must tell GraniteDS which classes we want to externalize with a special rule in the granite-config.xml file:



<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE granite-config PUBLIC
    "-//Granite Data Services//DTD granite-config internal//EN"
    "http://www.graniteds.org/public/dtd/3.0.0/granite-config.dtd">

<granite-config>
    <class-getter type="org.granite.hibernate.HibernateClassGetter"/>

    <externalizers>
        <externalizer type="org.granite.hibernate.HibernateExternalizer">
            <include type="com.myapp.entity.Person"/>
        </externalizer>
    </externalizers>
</granite-config>
            

This instructs GraniteDS to externalize all classes named com.myapp.entity.Person by using the org.granite.hibernate.HibernateExternalizer. Note that the HibernateClassGetter configuration is necessary to detect Hibernate proxies (lazy-initialized beans). See more about this feature in the JPA and lazy initialization section.

If you use an abstract entity bean as a parent to all your entity beans you could use this declaration, but note that type in the example above is replaced by instance-of:



<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE granite-config PUBLIC
    "-//Granite Data Services//DTD granite-config internal//EN"
    "http://www.graniteds.org/public/dtd/3.0.0/granite-config.dtd">

<granite-config>
    <class-getter type="org.granite.hibernate.HibernateClassGetter"/>

    <externalizers>
        <externalizer type="org.granite.hibernate.HibernateExternalizer">
            <include instance-of="com.myapp.entity.AbstractEntity"/>
        </externalizer>
    </externalizers>
</granite-config>
            

This will avoid the need of writing externalization instructions for all your beans, and all instances of AbstractEntity will be automatically externalized.

You may also use an annotated-with attribute as follows:



<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE granite-config PUBLIC
    "-//Granite Data Services//DTD granite-config internal//EN"
    "http://www.graniteds.org/public/dtd/3.0.0/granite-config.dtd">

<granite-config>
    <class-getter type="org.granite.hibernate.HibernateClassGetter"/>

    <externalizers>
        <externalizer type="org.granite.hibernate.HibernateExternalizer">
            <include annotated-with="javax.persistence.Entity"/>
            <include annotated-with="javax.persistence.MappedSuperclass"/>
            <include annotated-with="javax.persistence.Embeddable"/>
        </externalizer>
    </externalizers>
</granite-config>
            

Of course, you may mix these different attributes as you want. Note, however, that there are precedence rules for these three configuration options: type has precedence over annotated-with and annotated-with has precedence over instance-of. Playing with rule precedence provides a way to override general rules with more specific rules for particular classes.

GraniteDS comes with a set of built-in externalizers for the most usual kinds of Java classes:

It is easy to write your own externalizer, you have to implement the org.granite.messaging.amf.io.util.externalizer.Externalizer interface, or extend the DefaultExternalizer class. There is no particular use case for this extension; it mostly depends on your specific needs and you should look at the standard externalizer implementations to figure out how to write your custom code.

If you use autoscan configuration, make sure your class is packaged in a jar accessible via the GraniteConfig class loader (granite.jar classpath), put a META-INF/granite-config.properties in your jar, even empty, and put relevant code in the accept method to define which classes your externalizer should process:



public int accept(Class<?> clazz) {
    return clazz.isAnnotationPresent(MySpecialAnnotation.class) ? 1 : -1;
}
            

You may, of course, use any kind of conditional expression, based on annotations, inheritance, etc. The returned value is a numeric weight used when GDS tries to figure out what externalizer it should use when it encounters a Java bean at serialization time: -1 means "do not use this externalizer", 0 or more means "use this externalizer if there is no other externalizer that returns a superior weight for this bean". DefaultExternalizer has a weight of 0, EnumExternalizer and the built-in JPA externalizers a weight of 1. If your class would normally be externalized by the HibernateExternalizer, you may, for example, use a weight of 2 when you want to replace the default serialization for some particular entities.

At deserialization time, from client to server, GraniteDS must instantiate and populate new JavaBeans with serialized data. The population issue (strictly private field), as we have seen before, is addressed by externalizers. But there is still a problem with classes that do not declare a default constructor. How do we instantiate those classes with meaningful parameters at deserialization time?

When GraniteDS encounters classes without a default constructor, it tries to instantiate them by using the Sun JVM sun.reflect.ReflectionFactory class that bypasses this limitation. Then, if it can successfully instantiate this kind of class, fields deserialization follows the standard process with or without externalization. This solution has three serious limitations however: it only works with a Sun JVM, it does not take care of complex initialization you may have put in your custom contructor, and it cannot simply work with classes that should be created via a static method, such as singletons.

With GraniteDS instantiators, you may control the instantiation process, delaying the actual instantiation of the class after all its serialized data has been read.

Built-in instantiators

Two instantiators come with GDS:

Note that those instantiators do not require an entry in granite-config.xml, they are respectively used by the EnumExternalizer, HibernateExternalizer, and TopLinkExternalizer.

Custom instantiators

Let's say you have a JavaBean like this one:



package org.test;
import java.util.Map;
import java.util.HashMap;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
public class MyBean {
    private final static Map<String, MyBean> beans = new HashMap<String, MyBean>();
    private final String name;
    private final String encodedName;
    protected MyBean(String name) {
        this.name = name;
        try {
            this.encodedName = URLEncoder.encode(name, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
    public static MyBean getInstance(String name) {
        MyBean bean = null;
        synchronized (beans) {
            bean = beans.get(name);
            if (bean == null) {
                bean = new MyBean(name);
                beans.put(name, bean);
            }
        }
        return bean;
    }
    public String getName() {
        return name;
    }
    public String getEncodedName() {
        return encodedName;
    }
}
            

With this kind of Java class, even with the help of the GDS DefaultExternalizer and the Sun ReflectionFactory facility, you will not be able to get the cached instance of your bean and the encodedName field will not be correctly initialized. Instead, a new instance of MyBean would be created with a simulated default constructor and the name field would be assigned with serialized data.

The solution is to write a custom instantiator that will be used at deserialization time:




package org.test;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import org.granite.messaging.amf.io.util.instantiator.AbstractInstanciator;
public class MyBeanInstanciator extends AbstractInstanciator<MyBean> {
    private static final long serialVersionUID = -1L;
    private static final List<String> orderedFields;
    static {
        List<String> of = new ArrayList<String>(1);
        of.add("name");
        orderedFields = Collections.unmodifiableList(of);
    }
    @Override
    public List<String> getOrderedFieldNames() {
        return orderedFields;
    }
    @Override
    public MyBean newInstance() {
        return MyBean.getInstance((String)get("name"));
    }
}
            

You should finally use a granite-config.xml file as follows in order to use your instantiator:



<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE granite-config PUBLIC
    "-//Granite Data Services//DTD granite-config internal//EN"
    "http://www.graniteds.org/public/dtd/3.0.0/granite-config.dtd">

<granite-config>
  <externalizers>
    <externalizer type="org.granite.messaging.amf.io.util.externalizer.DefaultExternalizer">
      <include type="org.test.MyBean"/>
    </externalizer>
  </externalizers>

  <instanciators>
    <instanciator type="org.test.MyBean">org.test.MyBeanInstanciator</instanciator>
  </instanciators>
</granite-config>
            

In many Java EE applications, persistence is done by using a JPA provider (such as Hibernate). The application directly persists and fetch Java entities, so this could seem natural to transfer these same objects to the client layer instead of adding a extra conversion layer with data transfer objects. However this is not as simple as it seems, in particular when using the lazy loading feature of JPA (and most applications using JPA should use lazy loading).

Usual serialization providers (AMF or not) will either throw exceptions during serialization (because the lazy loaded associations are not available at this time), or load the complete object graph and thus limit the applicability of lazy loading (when using patterns such as Open Session in View).

GraniteDS on the other hand is able to reliably serialize JPA entities with its externalizer mechanism (even detached objects outside of a JPA session) and supports both kinds of associations: proxy (single-valued associations) and collections (such as List, Set, Bag and Map). As described in the previous section, it provides built-in support for Hibernate, TopLink/EclipseLink, OpenJPA and DataNucleus.

GDS also provides a way to keep uninitialized collections as is. When the externalizer encounters an uninitialized collection, it does not try to serialize its content and marks it as uninitialized. This information is kept in client beans and when this bean is sent back to the server (e.g., for an update), the externalizer restores a lazy initialized collection in Java. This gives you a good control over serialization depth, as you do not face the risk of serializing the entire graph of your data, and prevents faulty updates (i.e., an empty collection is saved and deletes database data while it was only uninitialized).

For example, in this persistent set:



package com.myapp.entity;
import java.util.HashSet;
import java.util.Set;
...
import javax.persistence.CascadeType;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
@Entity
public class Person extends AbstractEntity {
    ...
    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="person")
    private Set<Contact> contacts = new HashSet<Contact>();
    ...
    public Set<Contact> getContacts() {
        return contacts;
    }
    public void setContacts(Set<Contact> contacts) {
        this.contacts = contacts;
    }
}
        // code for Contact skipped...
            


package com.myapp.entity;
    ...
    import javafx.collections.ObservableList;
    @JavaFXObject
    @RemoteClass("test.granite.ejb3.entity.Person")
    public class Person implements Identifiable, Lazyable, DataNotifier  {
        ...
        private ObservableList<Contact> contacts = new PersistentSet<Contact>();
        ...
        public void setContacts(ObservableList<Contact> contacts) {
            this.contacts = value;
        }
        public ObservableList<Contact> getContacts() {
            return this.contacts;
        }
        // code for Contact skipped...
            

The actual, persistence aware, ObservableList implementation is part of a GDS JavaFX client library (granite-javafx-client.jar) that contains all you need in order to use the lazy loaded collections feature.

If GDS encounters an uninitialized Set, it is serialized as a org.granite.messaging.persistence.ExternalizablePersistentSet that contains some extra data indicating its intitialization state. Other persistent collections, such as List, Bag, and Map, are handled in a similar manner.

GDS/JPA uses the interface Identifiable that requires a readable property uid for all entity beans. See a long Hibernate discussion here about equals/hashCode/collection problems and the use of UUIDs. This is only an implementation choice and you are free to code whatever you want, for example generate the uid from a natural identifier or from the database key.

Security in a Java client cannot simply rely on standard web-app security-constraints configured in web.xml. Generally, you have only one channel-definition, equivalent to a url-pattern in web.xml, and multiple destinations. So, the security must be destination-based rather than URL-pattern based, and Java EE standard configuration in web.xml does not provide anything like that.

With a configured SecurityService, you will be able to use Channel's setCredentials and logout methods.

Another important feature in security is to be able to create and expose a java.security.Principal to, for example, an EJB3 session bean backend so role-based security can be used.

At this time, GraniteDS provides security service implementations for Tomcat5+, Jetty6+, GlassFish V2+ and V3 and WebLogic 10+ servers. Because JBoss comes with Tomcat by default but may be configured to use Jetty instead, Tomcat or Jetty security services may work as well with JBoss.

When you are using Java Enterprise frameworks such as Seam or Spring together with GraniteDS, you may use specific Seam Security or Spring Security implementations instead of the previous container-based services: please refer to Seam Services or Spring Services for more information.

To enable security, you simply put this kind of declaration in your granite-config.xml file:



<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE granite-config PUBLIC
    "-//Granite Data Services//DTD granite-config internal//EN"
    "http://www.graniteds.org/public/dtd/3.0.0/granite-config.dtd">
<granite-config>
    ...
    <security type="org.granite.messaging.service.security.TomcatSecurityService"/>
    <!--
    Alternatively for Tomcat 7.x
    <security type="org.granite.messaging.service.security.Tomcat7SecurityService"/>
    Alternatively for Jetty 6.x
    <security type="org.granite.messaging.service.security.Jetty6SecurityService"/>
    For Jetty 7.x/8.x (available at eclipse.org)
    <security type="org.granite.messaging.service.security.Jetty7SecurityService"/>
    For GlassFish 2.x
    <security type="org.granite.messaging.service.security.GlassFishSecurityService"/>
    For GlassFish 3.x
    <security type="org.granite.messaging.service.security.GlassFishV3SecurityService"/>
    For WebLogic
    <security type="org.granite.messaging.service.security.WebLogicSecurityService"/>
    -->
</granite-config>
            

Some of these implementations (currently only TomcatSecurityService) accept an optional parameter. In the case of the Tomcat service, it's the name of the service that will be used to execute the authentication in case you have many services defined in your server.xml.



<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE granite-config PUBLIC
    "-//Granite Data Services//DTD granite-config internal//EN"
    "http://www.graniteds.org/public/dtd/3.0.0/granite-config.dtd">
<granite-config>
    ...
    <security type="org.granite.messaging.service.security.TomcatSecurityService">
        <param name="service" value="your-tomcat-service-name-here"/>
    </security>
</granite-config>        
            

You may now use role-based security on destination in your services-config.xml file:



<?xml version="1.0" encoding="UTF-8"?>
<services-config>
    <services>
        <service id="granite-service"
            class="flex.messaging.services.RemotingService"
            messageTypes="flex.messaging.messages.RemotingMessage">
            <destination id="person">
                <channels>
                    <channel ref="my-graniteamf"/>
                </channels>
                <properties>
                    <scope>session</scope>
                    <source>com.myapp.PersonService</source>
                </properties>
                <security>
                    <security-constraint>
                        <auth-method>Custom</auth-method>
                        <roles>
                            <role>user</role>
                            <role>admin</role>
                        </roles>
                    </security-constraint>
                </security>
            </destination>

            <destination id="restrictedPerson">
                <channels>
                    <channel ref="my-graniteamf"/>
                </channels>
                <properties>
                    <scope>session</scope>
                    <source>com.myapp.RestrictedPersonService</source>
                </properties>
                <security>
                    <security-constraint>
                        <auth-method>Custom</auth-method>
                        <roles>
                            <role>admin</role>
                        </roles>
                    </security-constraint>
                </security>
            </destination>
        </service>
    </services>
    ...
</services-config>
            

Here, the person destination can be used by authenticated users with user or admin roles, while the restrictedPerson destination can only be used by authenticated users with the admin role.

Please refer to Tomcat and JBoss documentation for setting up your users/roles configuration.

At Java side, AMF deserialization instantiates classes that are referenced in the binary-encoded request coming from the client. Thus, a malicious AMF3 request can be crafted in order to instantiate an arbitrary Java class (and execute its constructor and setters) that has nothing to do with the expected data exchanged between the client application and the server application.

GraniteDS’ fix for this security issue relies on a new configurable option that you can put in your granite-config.xml file. If you don’t configure anything, you will always see this warning at the startup of the application:

WARN  [GraniteConfig] You should configure a deserializer securizer in your granite-config.xml file in order to prevent potential security exploits!

In order to secure your application, you are strongly encouraged to configure a securizer as follows:



<!DOCTYPE granite-config PUBLIC
  "-//Granite Data Services//DTD granite-config internal//EN"
  "http://www.graniteds.org/public/dtd/3.0.0/granite-config.dtd">
 
<granite-config scan="true">
 
  <amf3-deserializer-securizer param="
    org\.granite\..* |
    flex\.messaging\..* |
    com\.myapp\.entity\..*
  "/>
    
  ...
</granite-config>
           

By default, the securizer uses the org.granite.messaging.amf.io.RegexAMF3DeserializerSecurizer class that, uses a regular expression parameter. Only classes whose name match one of theses patterns are allowed to be instantiated. Of course, all standard Java types are allowed by default and you don’t have to explicitely add their package names expressions.

If this default regex-based implementation doesn’t fit your needs, you may write your own securizer implementation. It only has to implement the org.granite.messaging.amf.io.AMF3DeserializerSecurizer interface and can be specified in granite-config.xml:



<!DOCTYPE granite-config PUBLIC
  "-//Granite Data Services//DTD granite-config internal//EN"
  "http://www.graniteds.org/public/dtd/3.0.0/granite-config.dtd">
 
<granite-config scan="true">
 
  <amf3-deserializer-securizer type="com.myapp.MySecurizer"/>
  ...
</granite-config>