graniteds.orgCommunity Documentation

Chapter 4. Remoting and Serialization

4.1. Using the RemoteObject API
4.1.1. RemoteObject in MXML
4.1.2. RemoteObject in ActionScript
4.1.3. RemoteObject in ActionScript without services-config.xml file
4.1.4. Using HTTPS
4.2. Using the Tide API
4.2.1. Basic remoting
4.2.2. Basic remoting with dependency injection
4.2.3. Using the ITideResponder Interface
4.2.4. Simplifying asynchronous interactions
4.2.5. Service Initializer
4.2.6. Client Message Interceptors
4.2.7. Global Exception Handling
4.2.8. Miscellaneous Features
4.3. Mapping Java and AS3 objects
4.4. Externalizers and AS3 Code Generation
4.4.1. Example of a JPA entity and its corresponding AS3 beans
4.4.2. Standard Configuration
4.4.3. Autoscan Configuration
4.4.4. Built-in Externalizers
4.4.5. Custom Externalizers
4.4.6. @ExternalizedBean and @ExternalizedProperty
4.4.7. Custom Class Getters
4.4.8. Instantiators
4.5. JPA and Lazy Initialization
4.5.1. Single-Valued Associations (proxied or weaved associations)
4.5.2. Collections (List, Set, Bag, Map)
4.6. Securing Remote Destinations
4.6.1. Configuration
4.6.2. SecureRemoteObject
4.6.3. Fine-grained Per-destination Security

Data serialization between a Flex client application and a J2EE server may use three kinds of transfer encoding:

According to all available benchmarks, the last option, AMF3 with RemoteObject, is the faster and most efficient. Additionally it allows to work with strongly typed objects in the Flex application and thus is more maintainable. GraniteDS provides a full implementation of the AMF3 protocol and a set of adapters suitable for remote calls to POJO, EJB 3, Seam, Spring, and Guice services.

However, standard AMF serialization/deserialization does not provide any way, either with LiveCycle Data Services/BlazeDS or with GraniteDS, to transfer private or protected data fields. Only non-static, non-transient public fields, either those with public getter and setter or with a public declaration, are taken into account. This limitation applies to both Java and ActionScript3 classes.

To preserve strong and secure data encapsulation of your beans while serializing their private internal state - such as a version number in entity beans — GraniteDS provides a specific serialization mechanism called externalization. See corresponding section for details.

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 Flash 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.

RemoteObject is the standard remoting API of the Flex SDK. It can be use either declaratively in MXML or programmatically in ActionScript. A RemoteObject is attached to a server-side destination, generally defined in the services-config.xml (see the configuration reference). You can also refer to the Adobe Flex SDK documentation about RemoteObject to get some useful information.

For this example, we'll show a simple POJO destination :



public class HelloService {
    public String hello(String name) {
        return "Hello " + name; 
    }
}
            


<services>
    <service
        id="granite-service"
        class="flex.messaging.services.RemotingService"
        messageTypes="flex.messaging.messages.RemotingMessage">
        <destination id="hello">
            <channels>
                <channel ref="graniteamf"/>
            </channels>
            <properties>
                <scope>request</scope>
                <source>com.myapp.HelloService</source>
            </properties>
        </destination>
    </service>
</services>

<channels>
    <channel-definition id="graniteamf" class="mx.messaging.channels.AMFChannel">
        <endpoint
            uri="http://{server.name}:{server.port}/{context.root}/graniteamf/amf"
            class="flex.messaging.endpoints.AMFEndpoint"/>
    </channel-definition>
</channels>
            

This service configuration defines an AMF channel and a simple POJO destination named hello mapped to this channel and which source is the Java class we have created. POJO is the default service adapter so we don't have to specify a particular service factory.



<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">

    <mx:Script>
        import mx.rpc.events.ResultEvent;
        import mx.rpc.events.FaultEvent;
        import mx.controls.Alert;
        
        public function resultHandler(event:ResultEvent):void {
            // Display received message
            outputMessage.text = event.result as String;
        }                       
        
        public function faultHandler(event:FaultEvent):void {
            // Show error alert
            Alert.show(event.fault.faultString);               
        }
    </mx:Script>
    
    <!-- Connect to a service destination.--> 
    <mx:RemoteObject id="helloService" 
        destination="hello"
        result="handleResult(event);"
        fault="handleFault(event);"/>
    
    <!-- Provide input data for calling the service. --> 
    <mx:TextInput id="inputName"/>
    
    <!-- Call the web service, use the text in a TextInput control as input data.--> 
    <mx:Button click="helloService.hello(inputName.text)"/>
    
    <!-- Display results data in the user interface. --> 
    <mx:Label id="outputMessage"/>
</mx:Application>
            

This demonstrates a very simple remote call with basic String data types. The destination defined in the MXML RemoteObject declaration should match the destination name in services-config.xml.

It is very important to note that remote calls in Flex are always asynchronous. The reason is that the Flash VM is not multithreaded and remote calls should not block user interaction. Something like outputMessage.text = helloService.hello(inputName.text) will thus not work, and it is needed to attach event listeners to the RemoteObject to handle the remote results and faults.

The actual return value of a remote call on a RemoteObject is an AsyncToken object. The MXML syntax result and fault is simply a shorthand for adding listeners to this token object.

In this short example, there was only one method in the RemoteObject so we could put the event listeners on the RemoteObject itself. For services having more than one method, we would rather add a different event listener for each method :



<mx:RemoteObject id="helloService" 
        destination="hello">
    <mx:operation name="hello" 
        result="handleResult(event);"
        fault="handleFault(event);"/>
    <mx:operation name="..."
        result="..."
        fault="..."/>
</mx:RemoteObject>
            

The last but interesting way of handing the remote result is to bind the AsyncToken property lastResult to some UI component in MXML. The following code does the same thing than the initial example :



<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">

    <!-- Connect to a service destination.--> 
    <mx:RemoteObject id="helloService" destination="hello"/>
    
    <!-- Provide input data for calling the service. --> 
    <mx:TextInput id="inputName"/>
    
    <!-- Call the web service, use the text in a TextInput control as input data.--> 
    <mx:Button click="helloService.hello(inputName.text)"/>
    
    <!-- Display results data in the user interface using binding on the lastResult property of AsyncToken. --> 
    <mx:Label id="outputMessage" text="{helloService.hello.lastResult}"/>
</mx:Application>
            

It is possible to use more complex data types as arguments or as result values. It is then necessary to create an equivalent ActionScript 3 class for each Java data class. You can refer to the mapping section to see how to do this in detail. Also see how you can use the Gas3 code generator to do this for you.



package com.myapp.model;
public class Person {
    private String name;
    public String getName() { 
        return name; 
    }
    public void setName(String name) { 
        this.name = name;
    }
}
            
package com.myapp.model {
		
	[RemoteClass(alias="com.myapp.model.Person")]
	public class Person {
		public var name:String;
	}
}
	        


public class PeopleService {
    public List<Person> findAll(Person examplePerson) {
        ...
        return list;    
    }
}
            


<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">

    <!-- Connect to a service destination.--> 
    <mx:RemoteObject id="peopleService" 
        destination="people"
        result="handleResult(event);"
        fault="handleFault(event);"/>
    
    <!-- Provide input data for calling the service. --> 
    <mx:TextInput id="inputName"/>
    
    <!-- Call the web service, use the text in a TextInput control as input data.--> 
    <mx:Button click="peopleService.findAll(inputName.text)"/>
    
    <!-- Display results data in the user interface. --> 
    <mx:DataGrid id="outputGrid" dataProvider="{peopleService.lastResult}"/>
</mx:Application>
            

The Tide remoting API is an alternative to the standard RemoteObject. It can be used only programmatically in ActionScript and simplifies the handling of asynchronicity by hiding AsyncToken and other internal objects. Note that Tide provides much more than just a different API, it will be detailed 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.



<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Script>
        import org.granite.tide.Tide;
        import org.granite.tide.Context;
        import org.granite.tide.events.TideResultEvent;
        import org.granite.tide.events.TideFaultEvent;
        
        private var tideContext:Context = Tide.getInstance().getContext();
        
        private function hello(name:String):void {
            // tideContext.helloService implicitly creates a proxy for the remote destination named helloService
            tideContext.helloService.hello(name, resultHandler, faultHandler);
        }
        
        private function resultHandler(event:TideResultEvent):void {
            outputMessage.text = event.result as String;
        }                       
        
        private function faultHandler(event:TideFaultEvent):void {
            // Handle fault
        }
    </mx:Script>
    
    <!-- Provide input data for calling the service. --> 
    <mx:TextInput id="inputName"/>
    
    <!-- Call the web service, use the text in a TextInput control as input data.--> 
    <mx:Button click="hello(inputName.text)"/>
    
    <!-- Result message. --> 
    <mx:Label id="outputMessage"/>
</mx:Application>
            

This example can be cleaned up by using the dependency injection feature of the Tide framework (see here for more details). Basically you can inject a client proxy for a remote destination with the annotation [In].



<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    creationComplete="Tide.getInstance().initApplication()">
    <mx:Script>
        import org.granite.tide.Tide;
        import org.granite.tide.events.TideResultEvent;
        import org.granite.tide.events.TideFaultEvent;
        
        [In]
        public var helloService:Component;
        
        private function hello(name:String):void {
            helloService.hello(name, resultHandler, faultHandler);
        }
        
        private function resultHandler(event:TideResultEvent):void {
            outputMessage.text = event.result as String;
        }                       
        
        private function faultHandler(event:TideFaultEvent):void {
            // Handle fault
        }
    </mx:Script>
    
    <!-- Provide input data for calling the service. --> 
    <mx:TextInput id="inputName"/>
    
    <!-- Call the web service, use the text in a TextInput control as input data.--> 
    <mx:Button click="hello(inputName.text)"/>
    
    <!-- Result message. --> 
    <mx:Label id="outputMessage"/>
</mx:Application>
            

The server exceptions can be handled on the client-side by defining a fault callback on each remote call. It works fine 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 Flex 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 Flex side, you then have to define an exception handler class:

public class EntityNotFoundExceptionHandler implements IExceptionHandler {

    public function accepts(emsg:ErrorMessage):Boolean {
        return emsg.faultCode == "Persistence.EntityNotFound";
    }

    public function handle(context:BaseContext, emsg:ErrorMessage):void {
        Alert.show("Entity not found: " + emsg.message);
    }
}
            

... and register it as an exception handler for the Tide context in a static initializer block to be sure it is registered before anything else happens.



<mx:Application>
    <mx:Script>
        Tide.getInstance().addExceptionHandler(EntityNotFoundExceptionHandler);
    </mx:Script>
</mx:Application>
            

When using typed objects, it's necessary to create an ActionScript 3 class for each Java class that will be marshalled between Flex and Java. However due to the differences of data types in the ActionScript 3 and Java languages, data conversions are done during serialization/deserialization. GraniteDS follows the standard conversions specified in the Adobe Flex SDK documentation here, with an important exception : GDS will neither convert AS3 String to Java numeric types or boolean, nor AS3 numeric types or boolean to String. You must use AS3 numeric types for Java numeric types and AS3 boolean type for Java boolean types; either primitive or boxed boolean.

Starting with GraniteDS 2.2, long, Long, BigInteger and BigDecimal values may by converted to their respective ActionScript 3 equivalent (see Big Number Implementations for details).

In some cases it can be necessary to serialize private fields of a Java class (for example the @Version field of a JPA entity). Due to the limited capabilities of the ActionScript 3 reflection API than cannot access private fields, it is necessary to create an externalizable AS3 class (implementing flash.utils.IExternalizable and its corresponding externalizable Java class. In both classes you have to implement two methods readExternal and writeExternal that read and write data to the network stream in the exact same order. This is extremely tedious and unmaintainable, so GraniteDS provides a specific mechanism to handle this almost transparently :

By means of these two combined mechanisms, it's possible to serialize any kind of object with minimal effort.

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;
    }
}
			

This simple entity bean has one read-only property (id), one completely private property (version) and two read/write properties (firstName, lastName). With standard serialization, we would not be able to send the id and version fields to the Flex client code. One solution would be to make them public with getters and setters, but this would obviously expose these fields to manual and erroneous modifications. Another solution would be to make the person bean implement java.io.Externalizable instead of java.io.Serializable, but it would require implementing and maintaining the readExternal and writeExternal methods. This is at least an annoyance, a source of errors, and might even be impossible if you do not have access to the source code to the Java entities.

With GraniteDS automated externalization and without any modification made to our bean, we may serialize all properties of the Person class, private or not. Furthermore, thanks to the Gas3 code generator, we do not even have to write the ActionScript 3 bean by ourselves. Here is a sample generated bean implementation:

/**
 * Generated by Gas3 v2.2.0 (Granite Data Services).
 *
 * WARNING: DO NOT CHANGE THIS FILE. IT MAY BE OVERWRITTEN EACH TIME YOU USE
 * THE GENERATOR. INSTEAD, EDIT THE INHERITED CLASS (Person.as).
 */

package com.myapp.entity {

    import flash.utils.IDataInput;
    import flash.utils.IDataOutput;
    import flash.utils.IExternalizable;
    import org.granite.collections.IPersistentCollection;
    import org.granite.meta;

    use namespace meta;

    [Bindable]
    public class PersonBase implements IExternalizable {

        private var __initialized:Boolean = true;
        private var __detachedState:String = null;

        private var _firstName:String;
        private var _id:Number;
        private var _lastName:String;
        private var _version:Number;

        meta function isInitialized(name:String = null):Boolean {
            if (!name)
                return __initialized;

            var property:* = this[name];
            return (
                (!(property is Person) || (property as Person).meta::isInitialized()) &&
                (!(property is IPersistentCollection) ||
                  (property as IPersistentCollection).isInitialized())
            );
        }

        public function set firstName(value:String):void {
            _firstName = value;
        }
        public function get firstName():String {
            return _firstName;
        }

        public function get id():Number {
            return _id;
        }

        public function set lastName(value:String):void {
            _lastName = value;
        }
        public function get lastName():String {
            return _lastName;
        }

        public function readExternal(input:IDataInput):void {
            __initialized = input.readObject() as Boolean;
            __detachedState = input.readObject() as String;
            if (meta::isInitialized()) {
                _firstName = input.readObject() as String;
                _id = function(o:*):Number {
                    return (o is Number ? o as Number : Number.NaN) } (input.readObject());
                _lastName = input.readObject() as String;
                _version = function(o:*):Number {
                    return (o is Number ? o as Number : Number.NaN) } (input.readObject());
            }
            else {
                _id = function(o:*):Number {
                    return (o is Number ? o as Number : Number.NaN) } (input.readObject());
            }
        }

        public function writeExternal(output:IDataOutput):void {
            output.writeObject(__initialized);
            output.writeObject(__detachedState);
            if (meta::isInitialized()) {
                output.writeObject(_firstName);
                output.writeObject(_id);
                output.writeObject(_lastName);
                output.writeObject(_version);
            }
            else {
                output.writeObject(_id);
            }
        }
    }
}
	        

This AS3 bean reproduces all properties found in the Java entity, public and private and even includes two extra properties, (__initialized and __detachedState), that correspond the the JPA internal state for lazy loading. Note that these two fields are present because the Gas3 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.

Note that property accessors (get/set) are exactly the same as those found in the Java entity bean, and while all fields are serialized between the client and the server, only firstName and lastName are modifiable in ActionScript 3 and id is kept read-only.

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/2.3.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/2.3.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/2.3.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 Flex to Java, 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/2.3.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 Flex 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).

Classic AMF providers 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.

In your JPA entity bean, you may have a single-valued association like this:



@Entity
public class MyEntity {
    @Id @GeneratedValue
    private Integer id;
    @OneToOne(fetch=FetchType.LAZY)
    private MyOtherEntity other;
    // Skipped code...
}
@Entity
public class MyOtherEntity {
    @Id @GeneratedValue
    private Integer id;
    // Skipped code...
}
            

If you load a large collection of MyEntity and do not need other references, this kind of declaration prevents unnecessary performance and memory overload (please refer to Hibernate documention in order to actually fetch these references when you need them). With GDS, you can keep those uninitialized references as is. For example:

[Bindable]
[RemoteClass(alias="path.to.MyEntity"]
public class MyEntity {

    private var __initialized:Boolean = true;
    private var __detachedState:String = null;

    private var _id:Number;
    private var _other:MyOtherEntity;

    meta function isInitialized(name:String = null):Boolean {
        if (!name)
            return __initialized;

        var property:* = this[name];
        return (
            (!(property is Welcome) || (property as Welcome).meta::isInitialized()) &&
            (!(property is IPersistentCollection) ||
              (property as IPersistentCollection).isInitialized())
        );
    }

    // Skipped code...

    public override function readExternal(input:IDataInput):void {
        __initialized = input.readObject() as Boolean;
        __detachedState = input.readObject() as String;
        if (meta::isInitialized()) {
            _id = function(o:*):Number {
                return (o is Number ? o as Number : Number.NaN) } (input.readObject());
            _other = input.readObject() as MyOtherEntity;
            // read remaining MyEntity fields...
        }
        else
            _id = function(o:*):Number {
                return (o is Number ? o as Number : Number.NaN) } (input.readObject());
    }
}

[Bindable]
[RemoteClass(alias="path.to.MyOtherEntity"]
public class MyOtherEntity {

    private var __initialized:Boolean = true;
    private var __detachedState:String = null;

    private var _id:Number;

    // Skipped code...

    public override function readExternal(input:IDataInput):void {
        __initialized = input.readObject() as Boolean;
        __detachedState = input.readObject() as String;
        if (meta::isInitialized()) {
            _id = input.readObject() as int;
            // read remaining MyOtherEntity fields...
        }
        else
            _id = input.readObject() as int;
    }
}
	        

When Flex deserializes your collection of MyEntity's with lazy loaded MyOtherEntity references, it reads a initialized flag set to true when it encounters a MyEntity instance since MyEntity's are all initialized; so it reads all MyEntity fields including the _other one. When it deserializes a MyOtherEntity instance referenced by a MyEntity, it reads a initialized flag set to false since MyOtherEntity is lazy loaded, so it only reads the MyOtherEntity id. Informations put in __initialized, __detachedState and _id are sufficient to restore a correct HibernateProxy instances when you give back MyEntity objects to the server for update.

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 ActionScript 3 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 mx.collections.ListCollectionView;
    [Bindable]
    [RemoteClass(alias="test.granite.ejb3.entity.Person")]
    public class Person implements IExternalizable {
        ...
        private var _contacts:ListCollectionView;
        ...
        public function set contacts(value:ListCollectionView):void {
            _contacts = value;
        }
        public function get contacts():ListCollectionView{
            return _contacts;
        }
        ...
        public override function readExternal(input:IDataInput):void {
            ...
            _contacts = input.readObject() as ListCollectionView;
            ...
        }
        public override function writeExternal(output:IDataOutput):void {
            ...
            output.writeObject(_contacts);
            ...
        }
        // code for Contact skipped...
            

The actual, persistence aware, mx.collections.ListCollectionView implementation is part of a GDS Flex library (granite-essentials.swc) that contains all AS3 classes you need in order to use the lazy loaded collections feature.

If GDS encounters an uninitialized Set, it is serialized as a org.granite.persistence.PersistentSet that contains some extra data indicating its intitialization state.

Other persistent collections, such as List, Bag, and Map, are handled in a similar manner. Please download graniteds-***.zip and look at the examples/granite_ejb3 sample project for a more advanced data model.

GDS/JPA uses mx.core.IUID 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.

Security in Flex applications cannot 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 RemoteObject's setCredentials, setRemoteCredentials 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. For a complete example of a JBoss/Tomcat security service, please download and install graniteds-***.zip, read the examples/README.txt file and test the examples/granite_ejb3 sample.

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/2.3.0/granite-config.dtd">
<granite-config>
    ...
    <security type="org.granite.messaging.service.security.TomcatSecurityService"/>
    <!--
    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>
            

Generally there is no need to pass additional parameters, but TomcatSecurityService accepts one optional parameter for its internal server.findService() advanced utility, otherwise, the first available service is used by default:



<?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/2.3.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.