Search code examples
javaapache-flexactionscript-3blazedsjodatime

Custom Marshalling from Java to Flex via BlazeDS


My team are putting together a proof-of-concept Flex application sitting on top of a Spring-based server using BlazeDS.

We do quite a lot of date calculations, so we use Joda Time extensively throughout the code and in our domain model.

We're now trying to figure out how we can continue to use Joda Time in our DTOs that are sent back-and-forth with the Flex frontend via BlazeDS.

Our goal is to use the Actionscript 3 data type Date on the Flex side and have that map to our use of Joda time's DateTime, LocalDate and LocalTime types on the Java side.

We can solve the problem of converting Actionscript 3's Date type when calling Java with a custom type marshaller plugged into BlazeDS, but this appears to only be invoked for the Flex->Java/BlazeDS direction and not for the Java/BlazeDS->Flex direction.

I'm now looking at custom PropertyProxy implementations for BlazeDS, but this doesn't look like the right thing either.

The other idea was to implement Externalizable on our Java DTOs, but this seems like too much work, especially when I look at the BlazeDS rival GraniteDS and that shows plugging in Joda Time support in their documentation with a simple type converter!

Any ideas appreciated.


Solution

  • OK - I've found the answer on my own. This involved writing my own AMF endpoint class + related serializing classes. I've gotta say that the guys over at http://flexblog.faratasystems.com have been a great source of inspiration on hacking BlazeDS.

    This code should really be incorporated into BlazeDS itself or some Open Source extension project - it's so basic.

    Channel Definition

        <channel-definition id="my-amf" class="mx.messaging.channels.AMFChannel">
            <endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf" class="ch.hedgesphere.core.blazeds.endpoint.AMFEndpoint"/>
    
             <properties>
                <serialization>
                    <type-marshaller>ch.hedgesphere.core.blazeds.translator.HedgesphereASTranslator</type-marshaller>
                </serialization>
            </properties>
    
        </channel-definition>
    

    Custom AMF Endpoint

    package ch.hedgesphere.core.blazeds.endpoint;
    
    import ch.hedgesphere.core.blazeds.serialization.Serializer;
    
        public class AMFEndpoint extends flex.messaging.endpoints.AMFEndpoint {
    
        @Override
        protected String getSerializerClassName() {
            return Serializer.class.getName();
            }
    
        }
    

    Custom Serializer

    package ch.hedgesphere.core.blazeds.serialization;
    
    import java.io.OutputStream;
    
    import flex.messaging.io.MessageIOConstants;
    import flex.messaging.io.SerializationContext;
    import flex.messaging.io.amf.AmfMessageSerializer;
    import flex.messaging.io.amf.AmfTrace;
    
    public class Serializer extends AmfMessageSerializer {
    
        @Override
        public void initialize(SerializationContext context, OutputStream out, AmfTrace trace)
        {
            amfOut = new AMF0Output(context);
            amfOut.setOutputStream(out);
            amfOut.setAvmPlus(version >= MessageIOConstants.AMF3);
    
            debugTrace = trace;
            isDebug = trace != null;
            amfOut.setDebugTrace(debugTrace);
        }
    }
    

    Custom AMF 0 Handling

    package ch.hedgesphere.core.blazeds.serialization;
    
    import flex.messaging.io.SerializationContext;
    
    public class AMF0Output extends flex.messaging.io.amf.Amf0Output {
    
    public AMF0Output(SerializationContext context) {
        super(context);
    }
    
    @Override
        protected void createAMF3Output()
        {
            avmPlusOutput = new AMF3Output(context);
            avmPlusOutput.setOutputStream(out);
            avmPlusOutput.setDebugTrace(trace);
        }
    }
    

    Custom AMF 3 Handling

    package ch.hedgesphere.core.blazeds.serialization;
    
    import java.io.IOException;
    
    import org.joda.time.DateTime;
    import org.joda.time.LocalDate;
    import org.joda.time.LocalTime;
    
    import flex.messaging.io.SerializationContext;
    
    public class AMF3Output extends flex.messaging.io.amf.Amf3Output {
    
    public AMF3Output(SerializationContext context) {
        super(context);
    }
    
    @Override
    public void writeObject(Object value) throws IOException {
        if(value instanceof DateTime) {
            value = convertToDate((DateTime)value);
        }
        if(value instanceof LocalDate) {
            value = convertToDate((LocalDate)value);
        }
        if(value instanceof LocalTime) {
        value = convertToDate((LocalTime)value);
        }
        super.writeObject(value);
    }
    
    private Object convertToDate(LocalTime time) {
        return time.toDateTimeToday().toDate();
    }
    
    private Object convertToDate(LocalDate date) {
        return date.toDateMidnight().toDate();
    }
    
    private Object convertToDate(DateTime dateTime) {
        return dateTime.toDate();
    }   
    }
    

    Custom Marshaller for Flex->Java Calling

    package ch.hedgesphere.core.blazeds.translator;
    
    import org.joda.time.DateTime;
    import org.joda.time.LocalDate;
    import org.joda.time.LocalTime;
    
    import flex.messaging.io.amf.translator.ASTranslator;
    
    
    public class HedgesphereASTranslator extends ASTranslator {
    
    @SuppressWarnings({"rawtypes"})
    @Override
    public Object convert(Object originalValue, Class type) {
        if( type.equals(DateTime.class)) {
            return convertToDateTime(originalValue);
        }
        if( type.equals(LocalDate.class)) {
        return convertToLocalDate(originalValue); 
        }
        if( type.equals(LocalTime.class)) {
            return convertToLocalTime(originalValue);
        }
    
        return super.convert(originalValue, type);
    }
    
    private Object convertToLocalTime(Object originalValue) {
        return originalValue == null ? null : new LocalTime(originalValue);
    }
    
    private Object convertToLocalDate(Object originalValue) {
        return originalValue == null ? null : new LocalDate(originalValue); 
    }
    
    private Object convertToDateTime(Object originalValue) {
        return originalValue == null ? null : new DateTime(originalValue);
    }
    
    @SuppressWarnings({"rawtypes"})
    @Override
    public Object createInstance(Object source, Class type) {
        return super.createInstance(source, type);
    }
    }