Tuesday, February 16, 2010

Serializing parts of your Java Bean to AMF

I found a good example of how to serialize a Java object to AMF using BlazeDS here http://javadevelopmentforthemasses.blogspot.com/2008/08/amf-serialization-from-java-to-flex-and.html

But what happens if I don't want to serialize my model objects?
Suppose my entity has internalId, name and value fields and I only want to send the name and value fields.

According to BlazeDS developer guide I can solve this by setting fields as transient or implementing the Externalizable interface.

I can't set my internalId field as transient, nor do I want to implement Externalizable, which I would have to update every time I change my model object.
This can be solved using the PropertyProxyRegistry in the BlazeDS framework.

Let's define a new annotation - AMFTransient - this annotation will mark which fields should not be serialized.
package amf;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(value={ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AmfTransient
{
}

Now write our own new BeanProxy
package amf;

import java.lang.reflect.Field;
import java.util.List;

import flex.messaging.io.BeanProxy;

@SuppressWarnings("serial")
public class TransientAwareBeanProxy extends BeanProxy
{
 @SuppressWarnings("unchecked")
 @Override
 public List getPropertyNames( Object instance )
 {
  List propertyNames = super.getPropertyNames( instance );
  
  // find which fields where marked as transient and remove them
  Class c = instance.getClass();
  for ( Field field : c.getDeclaredFields() )
  {
   if ( field.isAnnotationPresent( AmfTransient.class ) )
   {
    propertyNames.remove( field.getName() );
   }
  }
  
  return propertyNames;
 }
}

Finally we have to register our Proxy in bootstrap code (each application probably does this differently)

PropertyProxyRegistry.getRegistry().register( Object.class, new TransientAwareBeanProxy() );

Finally, let's put it all together with our AMF serializer and Main class

package amf;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import flex.messaging.io.SerializationContext;
import flex.messaging.io.amf.Amf3Input;
import flex.messaging.io.amf.Amf3Output;

public class AmfSerializer
{
 public void toAmf( Object source, OutputStream os ) throws IOException
 {
  Amf3Output amf3Output = new Amf3Output( getSerializationContext() );
  amf3Output.setOutputStream( os );
  amf3Output.writeObject( source );
  amf3Output.flush();
  amf3Output.close();  
 }
 
 @SuppressWarnings("unchecked")
 public  T fromAmf( byte[] amf ) throws ClassNotFoundException, IOException
{
InputStream bIn = new ByteArrayInputStream( amf );
Amf3Input amf3Input = new Amf3Input( getSerializationContext() );
amf3Input.setInputStream( bIn );
return (T) amf3Input.readObject();
}

private SerializationContext getSerializationContext()
{
// Let the framework create the object and set the thread local variable
SerializationContext context = SerializationContext.getSerializationContext();
// set flags on the serialization context here
return context;
}
}

package amf;

import java.io.ByteArrayOutputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import flex.messaging.io.PropertyProxyRegistry;

public class Main
{
 public static void main( String[] args ) throws java.lang.Exception
 {
  // by default send objects to the transient aware proxy
  // you should set this in your bootstrap code
  // ff you don't want certain classes to use this proxy set the BeanProxy explicitly for them
  PropertyProxyRegistry.getRegistry().register( Object.class, new TransientAwareBeanProxy() );

  B toAmf = new B( "bid" );
  toAmf.as.put( "a1", new A( 11, "aid1" ) );
  toAmf.as.put( "a2", new A( 12, "aid2" ) );
  
  ByteArrayOutputStream bout = new ByteArrayOutputStream();
  AmfSerializer serializer = new AmfSerializer();
  serializer.toAmf( toAmf, bout );
  byte[] amf = bout.toByteArray();
  
  B fromAmf = serializer.fromAmf( amf );
  
  System.out.println( "this should be null: " + fromAmf.d );
  System.out.println( "this should be null: " + ((A)(fromAmf.as.values().toArray()[0])).id );
  System.out.println( "this should be a number: " + ((A)(fromAmf.as.values().toArray()[0])).i );
 }
 
 public static class A { 
  @AmfTransient public String id;
  private Integer i;
  
  public A() {} // required for desrializing AMF 

  public A( Integer i, String str ) {
   this.i = i;
   this.id = str;
  }
  public void setI( Integer i ) {
   this.i = i;
  }
  public Integer getI() {
   return i;
  }
  
  public boolean equals( Object obj ) {
   A tmp = (A)obj;
   return id.equals( tmp.id );
  }  
  public int hashCode() {
   return id.hashCode();
  }
 }
 
 public static class B {  
  @AmfTransient public Date d;
  public String id;
  public Map as = new HashMap();

  public B() {} // required for desrializing AMF

  public B( String id ) {
   this.id = id;
   d = new Date();
  }

  public boolean equals( Object obj ) {
   B tmp = (B)obj;
   return id.equals( tmp.id );
  }
  public int hashCode() {
   return id.hashCode();
  }
 }
}

1 comment:

  1. The Casino Floor, Casino, Diner & Spa - Mapyro
    This casino floor features 12 restaurants, 거제 출장마사지 16 bars, and 26 spa units. With the 충주 출장마사지 largest casino floor 출장샵 in the country, 상주 출장샵 it is recommended 수원 출장안마 that you book an

    ReplyDelete