There are some scenarios in which you want to pass objects to and from a Flex application to a Java back end, if your using an Adobe framework (LCDS or BlazeDS) than you get this functionality out of the box however there are some cases that you wish to escape Adobe's grip and uses some third party technology.
Some solutions that may come to mind are JSon or XML, the problem with these solutions is that they may require a lot of bandwidth and even lots of computing power for complex objects graphs, AMF to the rescue.
AMF is a binary protocol which is used natively by the flash player, with the release of BlazeDS it was made accessible to any Java client that wishes to use it, in order to use AMF we need to serialize an object into bytes and pass it as the payload of any protocol that we choose, the easiest way to do so is to encode the resulting AMF bytes into BASE64 encoding (a format that transforms byte arrays into a readable string form) and append it to the existing payload.
Ill start with the Java side first:
import com.google.inject.Inject;
import flex.messaging.endpoints.BaseHTTPEndpoint;
import flex.messaging.io.SerializationContext;
import flex.messaging.io.amf.Amf3Input;
import flex.messaging.io.amf.Amf3Output;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
/**
*
* @author ronen
*/
public class AmfSerializer {
@Inject
private SerializationContext context;
public <T> String toAmf(final T source) throws IOException {
final StringBuffer buffer = new StringBuffer();
final ByteArrayOutputStream bout = new ByteArrayOutputStream();
final Amf3Output amf3Output = new Amf3Output(context);
amf3Output.setOutputStream(bout);
amf3Output.writeObject(source);
amf3Output.flush();
amf3Output.close();
final BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(bout.toByteArray());
}
public <T> T fromAmf(final String amf) throws ClassNotFoundException, IOException {
final BASE64Decoder decoder = new BASE64Decoder();
byte[] input = decoder.decodeBuffer(amf);
InputStream bIn = new ByteArrayInputStream(input);
Amf3Input amf3Input = new Amf3Input(context);
amf3Input.setInputStream(bIn);
return (T) amf3Input.readObject();
}
}
This code uses BlazeDS API (the messaging-core, messaging-common and messaging-remoting jars) in order to serialize an object of type T into AMF byte array the bytes are encoded to a Base64 string, encoding result of an instance of the following VO:
package com.jdftm.vo;
import java.util.Date;
public class CurrentDayVO {
private Date now;
public CurrentDayVO() {
}
public void setNow(Date now) {
this.now = now;
}
public Date getNow() {
return now;
}
}
might look something like ChMzY29tLmpkZnRtLnZvLkN1cnJlbnREYXlWTwdub3cIAUJrjUw54AAA.
Now lets turn to the Flex side:
package com.jdftm.stomp.interop {
import flash.utils.ByteArray;
import mx.utils.Base64Encoder;
import mx.utils.Base64Decoder;
public class AMFSerializer {
public function serializeToString(value:Object):String{
if(value==null){
throw new Error("null isn't a legal serialization candidate");
}
var bytes:ByteArray = new ByteArray();
bytes.writeObject(value);
bytes.position = 0;
var be:Base64Encoder = new Base64Encoder();
be.encodeBytes(bytes);
var res:String = be.toString();
be.reset();
return res;
}
public function readObjectFromStringBytes(value:String):Object{
var dec:Base64Decoder=new Base64Decoder();
dec.decode(value);
var result:ByteArray=dec.drain();
result.position=0;
return result.readObject();
}
}
}
The logic is quite similar to the Java side code however when using the AMFSerializer we must register the serialized classes prior to serializing them:
registerClassAlias("com.jdftm.vo.CurrentDayVO", CurrentDayVO);
Failing to so will cause the de-serialization process (on both sides) to fail since it uses the alias in order to create the resulting instance object, its also required that the serialized classes on both ends to be in the same package as implemented in the CurrentDayVO Flex class:
package com.jdftm.vo{
[Bindable]
[RemoteClass(alias="com.jdftm.vo.CurrentDayVO")]
public class CurrentDayVO{
private var _now:Date;
public function get now():Date{
return _now;
}
public function set now(value:Date):void{
_now=value;
}
}
}
As youv seen AMF isn't to hard to use and may prove to be a powerful contender in the crowded integration protocols market.
Updated 2/09/08
Here is the SerializationContext implementation which is used in the Java serialization class
import com.google.inject.Provider;
import flex.messaging.io.SerializationContext;
public class SerializationContextProvider implements Provider<SerializationContext> {
@Override
public SerializationContext get() {
SerializationContext serializationContext = SerializationContext.getSerializationContext();// Threadlocal SerializationContent
serializationContext.enableSmallMessages = true;
serializationContext.instantiateTypes = true;
serializationContext.supportRemoteClass = true;// use _remoteClass field
serializationContext.legacyCollection = false;// false Legacy Flex 1.5 behavior was to return a java.util.Collection for Array, New Flex 2+ behavior is to return Object[] for AS3 Array
serializationContext.legacyMap = false;// false Legacy flash.xml.XMLDocument Type
serializationContext.legacyXMLDocument = false;// true New E4X XML Type
serializationContext.legacyXMLNamespaces = false;// determines whether the constructed Document is name-space aware
serializationContext.legacyThrowable = false;
serializationContext.legacyBigNumbers = false;
serializationContext.restoreReferences = false;
serializationContext.logPropertyErrors = false;
serializationContext.ignorePropertyErrors = true;
return serializationContext;
/*
serializationContext.enableSmallMessages = serialization.getPropertyAsBoolean(ENABLE_SMALL_MESSAGES, true);
serializationContext.instantiateTypes = serialization.getPropertyAsBoolean(INSTANTIATE_TYPES, true);
serializationContext.supportRemoteClass = serialization.getPropertyAsBoolean(SUPPORT_REMOTE_CLASS, false);
serializationContext.legacyCollection = serialization.getPropertyAsBoolean(LEGACY_COLLECTION, false);
serializationContext.legacyMap = serialization.getPropertyAsBoolean(LEGACY_MAP, false);
serializationContext.legacyXMLDocument = serialization.getPropertyAsBoolean(LEGACY_XML, false);
serializationContext.legacyXMLNamespaces = serialization.getPropertyAsBoolean(LEGACY_XML_NAMESPACES, false);
serializationContext.legacyThrowable = serialization.getPropertyAsBoolean(LEGACY_THROWABLE, false);
serializationContext.legacyBigNumbers = serialization.getPropertyAsBoolean(LEGACY_BIG_NUMBERS, false);
boolean showStacktraces = serialization.getPropertyAsBoolean(SHOW_STACKTRACES, false);
if (showStacktraces && Log.isWarn())
log.warn("The " + SHOW_STACKTRACES + " configuration option is deprecated and non-functional. Please remove this from your configuration file.");
serializationContext.restoreReferences = serialization.getPropertyAsBoolean(RESTORE_REFERENCES, false);
serializationContext.logPropertyErrors = serialization.getPropertyAsBoolean(LOG_PROPERTY_ERRORS, false);
serializationContext.ignorePropertyErrors = serialization.getPropertyAsBoolean(IGNORE_PROPERTY_ERRORS, true);
*/
}
}
This is the guice provider which is used when creating such contexts.