graniteds.orgCommunity Documentation
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.
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>
Using RemoteObject programmatically is necessary when called from a client controller class in a classic MVC pattern.
package com.myapp.controllers {
import mx.rpc.events.ResultEvent;
import mx.rpc.events.FaultEvent;
import mx.rpc.remoting.mxml.RemoteObject;
import mx.controls.Alert;
public class HelloController {
private var helloService:RemoteObject;
public function HelloController():void {
// Initialize a remote destination
helloService = new RemoteObject("pojo");
helloService.addEventListener(ResultEvent.RESULT, resultHandler, false, 0, true);
helloService.addEventListener(FaultEvent.FAULT, faultHandler, false, 0, true);
}
private function resultHandler(event:ResultEvent):void {
// Handler result
}
private function faultHandler(event:FaultEvent):void {
// Handle fault
}
}
}
When there is no services-config.xml (for example when the configuration is defined in the Spring or Seam configuration files),
it is necessary to manually initialize the endpoint for the RemoteObjects.
package com.myapp.controllers {
import mx.rpc.events.ResultEvent;
import mx.rpc.events.FaultEvent;
import mx.rpc.remoting.mxml.RemoteObject;
import mx.controls.Alert;
public class HelloController {
private var helloService:RemoteObject;
public function HelloController():void {
// Initialize a remote destination
helloService = new RemoteObject("hello");
helloService.source = "com.myapp.HelloService";
// Setup the channel set and endpoint for the RemoteObject
helloService.channelSet = new ChannelSet();
helloService.channelSet.addChannel(new AMFChannel("graniteamf",
"http://{server.name}:{server.port}/myapp/graniteamf/amf"));
helloService.addEventListener(ResultEvent.RESULT, resultHandler, false, 0, true);
helloService.addEventListener(FaultEvent.FAULT, faultHandler, false, 0, true);
}
private function resultHandler(event:ResultEvent):void {
// Handle result
}
private function faultHandler(event:FaultEvent):void {
// Handle fault
}
}
}
Using HTTPS involves two steps :
SecureAMFChannel instead of an AMFChannel in services-config.xmlweb.xml
<services>
...
</services>
<channels>
<channel-definition id="graniteamf" class="mx.messaging.channels.SecureAMFChannel">
<endpoint
uri="https://{server.name}:{server.port}/{context.root}/graniteamf/amf"
class="flex.messaging.endpoints.AMFEndpoint"/>
</channel-definition>
</channels>
<security-constraint>
<display-name>AMF access</display-name>
<web-resource-collection>
<web-resource-name>Secure AMF remoting</web-resource-name>
<description>Secure AMF Remoting</description>
<url-pattern>/graniteamf/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>role1</role-name>
...
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
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.
This section describes the usage of the Tide API with a standard AMF provider. When the Tide API is used in conjunction with GraniteDS and Tide-enabled server framework adapters, there are some specificities that are described in the chapters concerning each framework integration (EJB3, Spring, Seam 2, CDI).
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>
In some cases, you may need to pass some value to the result/fault handler to be able to distinguish different calls on the same method.
You can then implement the ITideResponder interface or use the default TideResponder implementation that is able to hold a token object:
public function call():void {
var responder1:TideResponder = new TideResponder(helloResult, helloFault, "firstCall");
var responder2:TideResponder = new TideResponder(helloResult, helloFault, "secondCall");
tideContext.helloWorld.sayHello("Jimi", responder1);
tideContext.helloWorld.sayHello("Jimi", responder2);
}
private function helloResult(event:TideResultEvent, token:Object):void {
if (token == "firstCall")
Alert.show(event.result);
}
In this case, the Alert will show up only once for the first call.
The ITideResponder interface has another important use : it makes possible to provide a return object that will be merged
with the server result. It greatly helps working with the asynchronous nature of Flex remoting by limiting the need for result handlers.
private var products:ArrayCollection = new ArrayCollection();
public function call():void {
tideContext.productService.findAllProducts(
new TideResponder(resultHandler, null, null, products)
);
}
private function resultHandler(event:TideResultEvent):void {
trace("Assert result was merged: " + (event.result === products));
}
<mx:DataGrid dataProvider="{products}">
...
</mx:DataGrid>
The result of the remote call will be merged in the provided products collection instance.
It is thus mandatory to provide a non null object instance, and this kind of merge will work with real objects and collections but not
with simple types (such as String, Number, ...), ...
Note that trying to merge a managed entity will work only if the received entity has the same uid than the source entity. This is a normal
behaviour to avoir breaking existing object associations in the local context. So this merge feature is mostly suitable for retrieving collections so you
are sure that the same instance of the collection is kept in sync.
Tide remoting can be used without needing the standard services-config.xml Flex configuration file.
In this case, it is necessary to manually define the remoting channels.
The easiest way is to setup the built-in default DefaultServiceInitializer component implementation in the Tide context,
for example in the creationComplete of the main application.
Tide.getInstance().addComponentWithFactory("serviceInitializer", DefaultServiceInitializer,
{ contextRoot: "/context-root" }
);
It is also possible to define serverName, serverPort and the url mappings for Granite AMF remoting
and for Gravity push graniteUrlMapping and gravityUrlMapping.
Tide additionally provides a built-in DefaultSecureServiceInitializer with the same options to setup a https channel.
Finally you can completely customize the channel initialization by providing your own implementation of IServiceInitializer.
It has only one method initialize that is called for all RemoteObjects or Consumers of the application.
If you need some common behaviour for all remote calls, such as showing/hiding a wait screen at each call or setting custom headers, you can implement a message interceptor that will be called before and after each call.
public class MyMessageInterceptor implements IMessageInterceptor {
public function before(msg:IMessage):void {
showWaitScreen();
msg.headers['customHeader'] = 'test';
}
public function after(msg:IMessage):void {
var customHeader:String = msg.headers['customHeader'] as String;
hideWaitScreen();
}
}
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 :
scan="true" in granite-config.xml, ensure that there is a
META-INF/granite-config.properties file (even empty) in the jar containing the exception converter class
(same principle than the seam.properties file to specify which jars need to be scanned in JBoss Seam 2.x).
granite-config.xml :
<exception-converters> <exception-converter type="com.myapp.custom.MyExceptionConverter"/> </exception-converters>
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>
There are a few other features that are useful when working with remote services :
Tide.showBusyCursor can enable or disable the busy mouse cursor during execution of remote calls.
Tide.busy is a bindable property that can be used to determine if there is currently a remote call in progress.
Tide.disconnected is a bindable property that can be used to determine if the network connection is currently broken.
If becomes false when a network error is detected and set to true after each successful call.
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 :
java.io.Externalizable manually. You just have to configure which classes should be processed.
writeExternal and readExternal methods.
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.
With the externalizer mechanism in GraniteDS, serializing data between Flex and Java is almost as powerful and flexible as pure Java serialization between a Java client and a Java server.
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.
Instead of configuring externalizers with the above method, you may use the autoscan feature:
<?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 scan="true"/>
With this very short configuration, GraniteDS will scan at startup all classes available in the classpath,
actually all classes found in the classloader of the GraniteConfig class, and discover all externalizers
(classes that implements the GDS Externalizer interface). The matching rule are defined implicitly by each externalizer,
for example the Hibernate externalizer is defined to match all classes annotated with @Entity.
GraniteDS comes with a set of built-in externalizers for the most usual kinds of Java classes:
org.granite.messaging.amf.io.util.externalizer.DefaultExternalizer: this externalizer may be used with any POJO bean.
org.granite.messaging.amf.io.util.externalizer.EnumExternalizer: this externalizer may be used with Java enum types. When autoscan is enabled, it will be automatically used for all enum types.
org.granite.hibernate.HibernateExternalizer: This externalizer may be used with all JPA/Hibernate entities (i.e., all classes annotated with @Entity, @MappedSuperclass or @Embeddable annotations). Include granite-hibernate.jar in your classpath in order to use this feature.
org.granite.toplink.TopLinkExternalizer: this externalizer may be used with all JPA/TopLink entities (i.e., all classes annotated with @Entity, @MappedSuperclass or @Embeddable annotations). Include granite-toplink.jar in your classpath in order to use this feature.
org.granite.eclipselink.EclipseLinkExternalizer: this externalizer will be used with the new version of TopLink (renamed EclipseLink). Include granite-eclipselink.jar in your classpath in order to use this feature.
org.granite.openjpa.OpenJpaExternalizer: this externalizer may be used with all JPA/OpenJPA (formerly WebLogic Kodo) entities (mainly in WebLogic environments). Include granite-openjpa.jar in your classpath in order to use this feature.
org.granite.datanucleus.DataNucleusExternalizer: this externalizer may be used with all JPA/DataNucleus entities. Include granite-datanucleus.jar in your classpath in order to use this feature.
org.granite.tide.cdi.TideEventExternalizer: this externalizer externalizes classes annotated with the TideEvent annotation.
org.granite.messaging.amf.io.util.externalizer.LongExternalizer: externalizes Java long or Long values.
org.granite.messaging.amf.io.util.externalizer.BigIntegerExternalizer: externalizes Java BigInteger values.
org.granite.messaging.amf.io.util.externalizer.BigDecimalExternalizer: externalizes Java BigDecimal values.
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.
Creating your own externalizer generally means that you also need to write a corresponding template for the Gas3 generator with matching
implementations of readExternal and writeExternal.
Two standard annotations are available that give you more control over the externalization process:
@ExternalizedBean: This class annotation may be used to instruct GDS to externalize the annotated bean
with the DefaultExternalizer or any other externalizer specified in the type attribute.
For example, you could annotate a Java class with:
@ExternalizedBean(type=path.to.MyExternalizer.class)
public class MyExternalizedBean {
...
}
@ExternalizedProperty: This method annotation may be used on a public getter when you want to externalize a property
with no corresponding field (i.e., a computed property). For example:
public class MyBean {
private int value;
...
@ExternalizedProperty
public int getSquare() {
return value * value;
}
}
Of course, this annotation will only be used if the MyBean class is configured for externalization.
Note that externalized properties are always read only: a setSquare(...) will never be used in the Flex to Java serialization.
Note also that Gas3 uses this annotation when it generates ActionScript3 bean so you'll find an extra square member field
in your generated MyBean.as.
A problem with the default AMF3 serialization is to get the true class name of an object in special cases. For example, a simple
myObject.getClass().getName() with a proxied entity bean would return org.hibernate.proxy.HibernateProxy instead of
the underlying entity bean class name. In order to get through this kind of problem, you must configure a class getter.
Other methods of ClassGetter are also used by Tide to determine some internal properties of the managed objects, such as their
JPA initialization state.
Class getters are generally used in conjunction with externalizers. For example, the full configuration for an application using Hibernate entities would be (without autoscan):
<?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="test.granite.ejb3.entity.AbstractEntity"/>
</externalizer>
</externalizers>
</granite-config>
The org.granite.hibernate.HibernateClassGetter class is used in order to retreive the correct entity class name from a proxy.
You may write and plug your own class getter in a similar way.
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.
Two instantiators come with GDS:
org.granite.messaging.amf.io.util.instantiator.EnumInstantiator: This instantiator is used in order to get an Enum constant
value from an Enum class and value (the String representation of the constant), by means of the
java.lang.Enum.valueOf(Class<? extends Enum> enumType, String name) method.
org.granite.hibernate.HibernateProxyInstantiator: It is used when GDS needs to recreate an HibernateProxy.
See source code for details.
Note that those instantiators do not require an entry in granite-config.xml, they are respectively used by the
EnumExternalizer, HibernateExternalizer, and TopLinkExternalizer.
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.
It is important to note that as a JPA detached entity can be reliably serialized between Flex and Java, it's perfectly possible (and even recommended) to directly persist or merge entities sent from the Flex application without any intermediate layer.
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.
scan set to false), you must use the appropriate class getter together with the persistence externalizer
(eg. org.granite.openjpa.OpenJpaClassGetter with org.granite.openjpa.OpenJpaExternalizer).
granite-hibernate.jar and granite-eclipselink.jar,
the application should work under both JBoss (which bundles Hibernate) and GlassFish 3 (which bundles EclipseLink).
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.
When using the RemoteObject API, the simplest way to use security in your Flex application is to use the
org.granite.rpc.remoting.mxml.SecureRemoteObject class.
This class brings advanced event-based security support as shown here:
...
import org.granite.rpc.remoting.mxml.SecureRemoteObject;
import org.granite.events.SecurityEvent;
...
private var srv:SecureRemoteObject = null;
...
public function init():void {
srv = new SecureRemoteObject("mydestination");
srv.addEventListener(SecurityEvent.ALL, onSecurityEvent);
...
}
public function onSecurityEvent(event:SecurityEvent):void {
switch (event.type) {
case SecurityEvent.INVALID_CREDENTIALS:
// show message "wrong username or pasword"
break;
case SecurityEvent.NOT_LOGGED_IN:
srv.logout(); // reset remote object
// show login panel...
break;
case SecurityEvent.SESSION_EXPIRED:
srv.logout(); // reset remote object
// show login panel...
break;
case SecurityEvent.ACCESS_DENIED:
// show message "you don't have rights..."
break;
}
}
public function onCredentialsSet(username:String, password:String):void {
srv.setCredentials(username, password);
...
}
public function doLogout():void {
srv.logout();
...
}
...
Note that you must compile your MXML/AS3 classes with the granite.swc library in order to use SecureRemoteObject.
You may write and configure a specific RemoteDestinationSecurizer in order to add fine grained security checks for specific actions.
public interface RemotingDestinationSecurizer extends DestinationSecurizer {
public void canExecute(ServiceInvocationContext context)
throws SecurityServiceException;
}
You then have to tell GraniteDS where to use your securizer:
<services-config>
<services>
<service ...>
<destination id="restrictedDestination">
...
<properties>
<securizer>path.to.MyDestinationSecurizer</securizer>
</properties>
</destination>
</service>
</services>
...
</services-config>
Note that securizers, if any, are always called before the standard SecurityService.authorize() method.