Wednesday, November 3, 2010

Quickest JPA test-setup ever!

Have you ever wanted to test some JPA functionality? Were you discouraged to do so because of the long setup process?
I wanted to test some JPA functionality without the hassle of setting up a DB and configuring XML files, A simple, short piece of code that I was able to run immediately.

This can be done very easily with in-memory HSQLDB and configuration using annotations. You only need Hibernate and HSQLDB on your classpath.
Take a look:

package jpa;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.List;

import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

import org.hibernate.ejb.Ejb3Configuration;

@Entity
public class SimpleEntityTest
{
    @Id
    @GeneratedValue
    private Long id;
    
    private String name;
    
    public SimpleEntityTest()
    {
    }
 
    public Long getId()
    {
        return id;
    }
    
    public void setId( Long id )
    {
        this.id = id;
    }
    
    public String getName()
    {
        return name;
    }
    
    public void setName( String name )
    {
        this.name = name;
    }
    
    @SuppressWarnings("unchecked")
    public static void main( String[] args ) throws Exception
    {
        Ejb3Configuration cfg = new Ejb3Configuration();
        cfg.setProperty( "hibernate.connection.driver_class", "org.hsqldb.jdbcDriver" );
        cfg.setProperty( "hibernate.connection.username", "sa" );
        cfg.setProperty( "hibernate.connection.password", "" );
        cfg.setProperty( "hibernate.connection.url", "jdbc:hsqldb:mem:test" );
        cfg.setProperty( "hibernate.dialect", "org.hibernate.dialect.HSQLDialect" );
        cfg.setProperty("hibernate.hbm2ddl.auto", "create-drop" );
        cfg.addAnnotatedClass( SimpleEntityTest.class );
        EntityManager em = cfg.buildEntityManagerFactory().createEntityManager();
        
        System.out.println( "create the entity" );
        EntityTransaction trx = em.getTransaction();
        trx.begin();
        SimpleEntityTest ent = new SimpleEntityTest();
        ent.setName( "test" );

        em.persist( ent );
        trx.commit();
     
        System.out.println( "change the entity" );
        trx = em.getTransaction();
        trx.begin();
        List entlist = em.createQuery( "from " + SimpleEntityTest.class.getName() ).getResultList();
        for ( Object object : entlist )
        {
            SimpleEntityTest ent1 = (SimpleEntityTest)object;
            ent1.setName( "other" );
        }
        trx.commit();

        // check with a connection that is not managed by Hibernate
        Connection con = DriverManager.getConnection( "jdbc:hsqldb:mem:test", "sa", "" );
        Statement stmt = con.createStatement();
        ResultSet rs = stmt.executeQuery( "select name from " + SimpleEntityTest.class.getSimpleName() );
        while ( rs.next() )
        {
            System.out.println( "Entity name: " + rs.getString( 1 ) );
        }
        rs.close();
        stmt.close();
        con.close();
   }
}

Even if you don't have anything installed you can be up and running in minutes using Maven (assuming you have Maven installed and working).
Follow 4 simple steps:
  • Create a Maven project: mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=jpa -DartifactId=jpa-test
  • Copy the above class to jpa-test/src/main/java/jpa
  • replace the pom.xml content with
    
     4.0.0
    
     jpa
     jpa-test
     1.0-SNAPSHOT
     jar
    
     jpa-test
     http://maven.apache.org
    
     
      UTF-8
     
    
     
      
       jboss.org
       Jboss Repo ORG
       default
       http://repository.jboss.org/maven2
       
        false
       
       
        true
       
      
     
    
     
      
       junit
       junit
       3.8.1
       test
      
      
       org.hibernate
       hibernate-core
       3.5.1-Final
      
      
       org.hibernate
       hibernate-annotations
       3.5.1-Final
      
      
       org.hibernate
       hibernate-entitymanager
       3.5.1-Final
      
      
       org.slf4j
       slf4j-api
       1.6.1
      
      
         org.slf4j
         slf4j-nop
         1.6.1
      
      
       hsqldb
       hsqldb
       1.8.0.10
      
     
    
     
      
       
        org.apache.maven.plugins
        maven-compiler-plugin
        
         1.6
         1.6
        
       
      
     
    
    
    
  • Build using Maven or your favorite IDE

Thursday, July 8, 2010

Running Play! framework JPA from a command line process

If you are using the Play! framework and wanted to run a command line process that uses the Play! JPA enhancements this post is for you.
It requires some classloader magic and is based on reading the source code of Play 1.0.x, I am not sure if this will be supported in future versions.

You need your Main class to prepare the Play! framework classes, set the classloader and load your "real" Main class using the Play! classloader.
This is how it's done:

public class LoaderMain
{
 public static void main( String[] args ) throws Exception
 {
        File root = new File(System.getProperty("application.path"));
        Play.init(root, System.getProperty("play.id", ""));
        Thread.currentThread().setContextClassLoader( Play.classloader );
        Class c = Play.classloader.loadClass( "com.incapsula.batch.PlayLoaderMain" );
        Method m = c.getMethod( "run" );
        m.invoke( c.newInstance() );
 }
}

Now since you are not invoking web services you need to execute the framework methods by yourself, e.g. initializing the plugins and openning a JPA transaction.
You'll have to get yourself familiar with the Play! framework source code for any dependencies your process has on the Play! frameowrk.

public class PlayLoaderMain
{
 public void run() throws Exception
 {
  new DBPlugin().onApplicationStart();
  new JPAPlugin().onApplicationStart();

  JPAPlugin.startTx( true );
  Fixtures.load( "initial-data.yml" );
  System.out.println( User.findAll() );
  JPAPlugin.closeTx( false );
 }
}

There are a few things to notice:
  • You need the Play jars in your classpath (play.jar, framework/lib jar files and module/lib jar files for every module you are using)
  • You need to point the "application.path" JVM property to your Play application
  • You need to initialize different Play! plugins if you need them (e.g. if you are using the Play! templates in your process you also need to initialize the MessagesPlugin)

Tuesday, July 6, 2010

JAXB, Sun and how to marshal a CDATA element

According to https://jaxb.dev.java.net/faq/index.html#marshalling_cdata there is no direct support in marshalling CDATA blocks, this is vendor specific.
I'll describe how this is done when using the built-in Sun implementation by an example.


Suppose this is my JAXB annotated class:
package org.oded;

import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.*;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Item
{
 @XmlAttribute public int id;
 
 public String text;
}

Next I run the Main class:
package org.oded;

import java.io.*;

import javax.xml.bind.*;

import com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler;

public class Main
{
 public static void main( String[] args ) throws Exception
 {
  Item i1 = new Item();
  i1.text = "hello";
  i1.id = 1;
  
  Item i2 = new Item();
  i2.text = "";
  i2.id = 2;
    
  Marshaller m = JAXBContext.newInstance( Item.class ).createMarshaller();
  
  m.marshal( i1, new OutputStreamWriter( System.out ) );
  System.out.println();
  m.marshal( i2, new OutputStreamWriter( System.out ) );
 }
}

The output is:
hello
<code><helloWorld/></code>

Now suppose I want to wrap the XML value of text in a CDATA element and avoid the escaping, I need to add specify an Adapter for text to surround the value in a CDATA element in the following way:

package org.oded;

import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.*;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Item
{
 @XmlAttribute public int id;
 
 @XmlJavaTypeAdapter(value=Adapter.class) 
 public String text;
 
 private static class Adapter extends XmlAdapter
 {

  @Override
  public String marshal( String v ) throws Exception
  {
   return "<![CDATA[" + v + "]]>";
  }

  @Override
  public String unmarshal( String v ) throws Exception
  {
   return v;
  }
  
 }
}

Now tell the Marshaller, in a Sun-specific way, not to escape the value of text:
package org.oded;

import java.io.*;

import javax.xml.bind.*;

import com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler;

public class Main
{
 public static void main( String[] args ) throws Exception
 {
  Item i1 = new Item();
  i1.text = "hello";
  i1.id = 1;
  
  Item i2 = new Item();
  i2.text = "";
  i2.id = 2;
    
  Marshaller m = JAXBContext.newInstance( Item.class ).createMarshaller();
  m.setProperty( "com.sun.xml.internal.bind.characterEscapeHandler", new CharacterEscapeHandler() {
   @Override
   public void escape( char[] ac, int i, int j, boolean flag, Writer writer ) throws IOException
   {
    // do not escape
    writer.write( ac, i, j );
   }
  });
  
  m.marshal( i1, new OutputStreamWriter( System.out ) );
  System.out.println();
  m.marshal( i2, new OutputStreamWriter( System.out ) );
 }
}

Now the output is:
<![CDATA[hello]]>
<![CDATA[]]>

Thursday, May 27, 2010

Using Play! precompiled classes

If you want to run Play! in PROD mode, after precompiling your code, you probably noticed that Play.usePrecompiled is set to false and doesn't change. This causes your code to be compiled when the PROD server starts.
I read somewhere that in the GAE module it's set to true.

Well, what if I want to deploy my Play! application without the sources and use my precompiled classes?
This only requires setting Play.usePrecompiled to true, writing a new module for this seems too much.
I thought the plugins architecture was the way to go, setting this in the onLoad() method, but without setting usePrecompiled Play! can not invoke my plugin.

I found an acceptable hack.
One of the first thing Play! does in the init() method is invoke initStaticStuff(), this method searches for files named "play.static" in the classpath, each line in the files must be a Java class name, and Class.forName() is invoked for each such class.
This doesn't do much but I can set the usePrecompiled value to true in a static initializer block.
I use a JVM property ("usePrecompile") to control the use of the precompiled classes.
I wrote a new class
package org.oded;

import play.Play;

public class Bootstrap {
 static {
  Play.usePrecompiled = Boolean.getBoolean( "usePrecompile" ) && Play.getFile( "precompiled" ).exists();
 }
}

I added a "play.static" file to my conf directory, the file has one line "org.oded.Bootstrap".

In addition I updated my build script, the one that invokes "play precompile", also creates a jar file in myserver/lib with only the Bootstrap file in it.

This is probably not the use that was intended for this hook, but it works.

Wednesday, May 12, 2010

Creating a log4j logger in an Eclipse template

private static Logger logger = Logger.getLogger( MyClass.class );
Are you tired of writing the same line of code over and over again?

No more!


Using the Eclipse templates you can generate this code easily.
Create the template by clicking Window -> Preferences -> Java -> Editor -> Templates -> New

Enter the template name
logger


Enter the pattern
private static Logger logger = Logger.getLogger( ${enclosing_type}.class );
${:import(org.apache.log4j.Logger)}
This pattern also takes care of the import statement.


Creating an Eclipse template




Now see it in action:




Thursday, April 15, 2010

Extension of log4j for logging in SaaS services

When developing SaaS you (hopefully) have many customers accessing your service.
Suppose one customer calls in and says that in a specific flow he has a problem (for example, when he asks for the lists of the assets he manages he gets HTTP error 500).
He's the only customer that complains about this flow, so it's something unique to this customer.
You look at the logs and you don't have enough information to figure out what went wrong (no surprise, you see your "ThisCanNeverHappenException").
The obvious thing to do is to change the log level to DEBUG and let the customer retry his flow. The side effect is frightening, your single log file is gigantic and you can't find your arms and legs anywhere. Debugging never looked this bad.

This project is an extension for the log4j framework allowing logging messages from different customers to different log files, with different logging levels for each customer:
http://code.google.com/p/saas-logging/

Sunday, March 21, 2010

Howto use Play! + Spring + your own properties file

For those of you who don't know Play!, Play! is a framework for developing Web applications in Java, without the adhering to the JEE specifications. It makes many things easier.

For example, Play! has a Spring integration module for effortless integration of your application with Spring.



Today I added my own properties file to my Spring-powered Play! app.

It sounds very simple, just add the following lines to your
application-context.xml:

   


and use it in your beans:

   ${myapp.somekey}


However, when you run your application you get the error:
Invalid bean definition with name 'a' defined in resource loaded
through SAX InputSource: Could not resolve placeholder
'myapp.somekey'
.

I spent some time trying to figure this out. Urrrghhhh.

Finally, I understood why this happens and how to avoid it.


The Play! Spring module uses a GenericApplicationContext and
problematically adds a PropertyPlaceholderConfigurer to it, allowing
you to substitute place holders with values in your application.conf
file. (I am not sure this feature is documented anywhere)

Now, when Spring invokes
AbstractApplicationContext.invokeBeanFactoryPostProcessors() it
invokes Play's PropertyPlaceholderConfigurer, which tries to convert $
{myapp.somekey}
as well.
Since it can't find ${myapp.somekey} in application.conf the exception
is thrown.

The way to "fool" the PropertyPlaceholderConfigurer Play! adds is to
override your application's place holder prefix and suffix.
This can be done in your application-context.xml file, for example:

   
   #[
   ]



   #[myapp.somekey]

Now your application can read properties from any properties file you
want.

Sunday, March 14, 2010

Dependency Injection and JPA

Consider the following classes


These are POJOs and also JPA entities.
Note that ShortPerson has a (transient) Ladder dependency.
When the JPA EntityManager loads the ShortPerson instances it does not supply the Ladder objects, thus when invoking the getObjectFromTopShelf() method on the short person object will fail.

I haven't found a best-practice on how to inject my dependencies.

I could write code in the ShortPerson constructor that "pulls" the dependency from the container, but that adds a dependency on the container, which must be initialized properly
before I create my ShortPerson instance.

So it should probably be done in the DAO, but how? Only one of my classes has the setLadder() method.


My solution is a combination of pulling the dependencies but from a runtime object.
Have another object (let's call it injector) know all of the possible dependencies of the various Person objects, and have each object pull it's own dependency from the injector.




So now Person has an empty implementation for inject() and ShortPerson can pull its ladder implementation from the injector, still decoupling ShortPerson from the dependency injection framework.
After the DAO loads the entities and before returning them to the client it invokes the inject() method for each instance.

The DAO itself can have the injector implementation injected into it during instantiation.

List persons = em.createQuery( "from Person" ).getResultList();
for ( Object object : entlist )
{
   Person person = (Person)object;
   person.inject( injector );
}

Sunday, March 7, 2010

Serializing Google Protocol Buffer to Blazeds AMF

What are these technologies and how can I use them?
Protocol buffers are a flexible, efficient, automated mechanism for serializing structured data.
You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.
read more

Action Message Format (AMF) is a binary format used to serialize ActionScript objects. It is used primarily to exchange data between an Adobe Flash application and a remote service, usually over the internet.
read more

BlazeDS is the server-based Java remoting and web messaging technology.
read more

OK. I read this and I am intrigued. How can I use these technologies together?

You start by defining your Protocol Buffer data structure and generate the matching Java classes.
Now you need to serialize your data structure to AMF using BlazeDS. Simple as that.

But wait, I read here that
For Java objects that BlazeDS does not handle implicitly, values found in public bean properties with get/set methods and public variables are sent to the client as properties on an Object. Private properties, constants, static properties, and read-only properties, and so on, are not serialized.
My generated data structures don't have setters, they use the Builder pattern. How can I tell BlazeDS what to serialize? I don't want to hack the code generation process and add implement my data structures as Externalizable.

You need to register your own PropertyProxy in the PropertyProxyRegistry.

I wrote a property proxy to help you out:
package amf;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

import com.google.protobuf.Descriptors;
import com.google.protobuf.GeneratedMessage;
import com.google.protobuf.Message;
import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;

import flex.messaging.io.ArrayCollection;
import flex.messaging.io.ArrayList;
import flex.messaging.io.BeanProxy;

/**
 * This is an implementation of the blazeds PropertyProxy for Google Protocol Buffers.
 */
@SuppressWarnings("unchecked")
public class GoogleProtocolBufferProxy extends BeanProxy
{
 private static ConcurrentHashMap propertiesCache = new ConcurrentHashMap();

private static final long serialVersionUID = -2175023450177522134L;

/**
* @see flex.messaging.io.PropertyProxy#getPropertyNames(java.lang.Object)
*/
public List getPropertyNames( Object instance )
{
List propertyNames = propertiesCache.get( instance.getClass() );
if ( propertyNames != null )
{
return propertyNames;
}

if ( instance instanceof Descriptors.EnumValueDescriptor )
{
Descriptors.EnumValueDescriptor enumValueDescriptor = (Descriptors.EnumValueDescriptor)instance;
String enumName = enumValueDescriptor.getType().getName();
return Collections.singletonList( enumName ); // an enum descriptor has a single enum value
}
else if ( instance instanceof GeneratedMessage )
{
// use GPB reflection to figure out the proprety names
GeneratedMessage message = (GeneratedMessage)instance;
List fields = message.getDescriptorForType().getFields();
propertyNames = new ArrayList( fields.size() );
for ( FieldDescriptor field : fields )
{
propertyNames.add( field.getName() );
}

propertiesCache.put( instance.getClass(), propertyNames );

return propertyNames;
}
else
{
throw new IllegalArgumentException( "Can only serialize GPB objects, not " + instance.getClass() );
}

}

/**
* @see flex.messaging.io.BeanProxy#getValue(java.lang.Object, java.lang.String)
*/
public Object getValue( Object instance, String propertyName )
{
if ( instance instanceof Descriptors.EnumValueDescriptor )
{
Descriptors.EnumValueDescriptor enumValueDescriptor = (Descriptors.EnumValueDescriptor)instance;
String enumValue = enumValueDescriptor.getName();
return enumValue;
}
if ( instance instanceof GeneratedMessage )
{
// use GPB reflection to figure out the proprety names
GeneratedMessage message = (GeneratedMessage)instance;
FieldDescriptor field = message.getDescriptorForType().findFieldByName( propertyName );
Object value = message.getField( field );
return value;
}
else
{
throw new IllegalArgumentException( "Can only serialize GPB objects, not " + instance.getClass() );
}
}

/**
* @see flex.messaging.io.AbstractProxy#createInstance(java.lang.String)
*/
public Object createInstance( String className )
{
if ( Descriptors.EnumValueDescriptor.class.getName().equals( className ) )
{
return new StringBuilder(); // use a StringBuilder to hold the value of the enum
}
try
{
Class clz = Class.forName( className );
Method newBuilderMethod = clz.getDeclaredMethod( "newBuilder", new Class[] {} );
newBuilderMethod.setAccessible( true );
Message.Builder builder = (Message.Builder)newBuilderMethod.invoke( null );
return builder;
}
catch ( ClassNotFoundException e )
{
throw new IllegalArgumentException( e );
}
catch ( NoSuchMethodException e )
{
throw new IllegalArgumentException( e );
}
catch ( InvocationTargetException e )
{
throw new IllegalArgumentException( e );
}
catch ( IllegalAccessException e )
{
throw new IllegalArgumentException( e );
}
catch ( SecurityException e )
{
throw new RuntimeException( e );
}
}

/**
* @see flex.messaging.io.BeanProxy#setValue(java.lang.Object, java.lang.String, java.lang.Object)
*/
public void setValue( Object instance, String propertyName, Object value )
{
if ( instance instanceof StringBuilder ) // this instance is an enum
{
StringBuilder builder = (StringBuilder)instance;
builder.append( value ); // save the enum value
}
else if ( instance instanceof Message.Builder ) // note the input is a message builder, not a message
{
Message.Builder builder = (Message.Builder)instance;
FieldDescriptor field = builder.getDescriptorForType().findFieldByName( propertyName );
if ( value instanceof Double )
{
Double dbl = (Double)value;
Object realValue = dbl;
// ActionScript Number is desrialized to Java Double 
// Java Double, Long, Float, are serialized to ActionScript Number
// here is the place to desrialized wisely
if ( field.getJavaType().equals( JavaType.LONG ) )
{
realValue = new Long( dbl.longValue() );
}
else if ( field.getJavaType().equals( JavaType.FLOAT ) )
{
realValue = new Float( dbl.floatValue() );
}
builder.setField( field, realValue );
}
else if ( field.getJavaType().equals( JavaType.ENUM ) && value instanceof StringBuilder ) // if this is an enum for which we saved the value set the correct enum value
{
EnumValueDescriptor enumValueDescriptor = field.getEnumType().findValueByName( String.valueOf( value ) );
builder.setField( field, enumValueDescriptor );
}
else if ( field.getJavaType().equals( JavaType.ENUM ) && value instanceof ArrayCollection ) // if this is an enum for which we saved the value set the correct enum value
{
ArrayCollection enumCollection = (ArrayCollection)value;
Iterator enumValuesIterator = enumCollection.iterator();
while ( enumValuesIterator.hasNext() )
{
StringBuilder enumValueBuilder = (StringBuilder)enumValuesIterator.next();
EnumValueDescriptor enumValueDescriptor = field.getEnumType().findValueByName( String.valueOf( enumValueBuilder ) );
builder.addRepeatedField( field, enumValueDescriptor );
}
}
else
{
builder.setField( field, value );
}
}
else
{
throw new IllegalArgumentException( "Can only serialize GPB objects, not " + instance.getClass() );
}
}

/**
* @see flex.messaging.io.AbstractProxy#instanceComplete(java.lang.Object)
*/
public Object instanceComplete( Object instance )
{
if ( instance instanceof StringBuilder )
{
return instance; // return the same string builder which holds the enum value
}
else if ( instance instanceof Message.Builder ) // note the input is a message builder, not a message
{
Message.Builder builder = (Message.Builder)instance;
return builder.build();
}
else
{
throw new IllegalArgumentException( "Can only serialize GPB objects, not " + instance.getClass() );
}
}

}

In your unit test don't forget to setup the registry. in JUnit it looks like this

@BeforeClass
public static void setup()
{
 PropertyProxyRegistry.getRegistry().register( GeneratedMessage.class, new GoogleProtocolBufferProxy() );
 PropertyProxyRegistry.getRegistry().register( Descriptors.EnumValueDescriptor.class, new GoogleProtocolBufferProxy() );
}

Happy serializing!

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