lundi 10 mai 2010

Mapping Hibernate entities in Flex

When working with entities that have been loaded or persisted with Hibernate, collections (such as the Map) are instanciated using Hibernate's own implementations (like the PersistentMap).
If you want to serialize and deserialize all Java Maps to a Flex custom class, this is an issue (as you may have PersistentMaps and HashMaps both returned from the server, simply depending on how they where created).

The following article gives the basic idea: http://famvdploeg.com/blog/2008/08/blazeds-and-hibernate-proxied-objects/

In my case, I used a custom Map implementation, called ExternalMap, that is used to transfer map data over BlazeDS (so that I can map it to my custom HashCollection on the client side).
In the HibernatePropertyProxy mentioned in the article, I simply overrode the following method:

@Override
  public Object getInstanceToSerialize(Object instance) {
    // TODO Auto-generated method stub
    Object instanceToSerialize = null;
    if (instance instanceof Map) {
      instanceToSerialize = new ExternalMap();
      PersistentMap map = (PersistentMap)instance;
      for (Object key : map.keySet()) {
        ((Map)instanceToSerialize).put(key, map.get(key));
      }
    }
    else {
      instanceToSerialize = super.getInstanceToSerialize(instance);
    }
    return instanceToSerialize;
  }

dimanche 18 avril 2010

Hibernate: moving objects from one parent to another

Using Hibernate, I usually default collection "cascade" attribute to "all-delete-orphan". The allows me to not care about cascading at all, and let Hibernate do everything.

However, if you want to move an object from one parent to another (in my case, say a peep is removed from a FarmTile and added to a BoardTile or a Card), you will get an exception like "deleted object would be re-saved by cascade (remove deleted object from associations".

To solve this, simply change the 'all-delete-orphan" to "all" - which means you will need to manually delete orphans if you need them deleted though.

dimanche 17 janvier 2010

Hibernate, BlazeDS, Java 5 enum and Flex custom collection (HashMap)

Context and problem

Here is the current situation:
  • On the server side, I am using Java and Hibernate for data storage. I am also using Java 5 enums.
  • The client is built in Flex. As mentioned in a previous post, I have gotten rid of "java-like enums", and now all constants are modeled with Strings
  • BlazeDS is in charge of the communication between the two.
 Concerning the data, on the Flex side I have a Farm that contains resources:

    [RemoteClass(alias="org.liveboardgames.agricola.domain.farm.Farm")]
    [Bindable]
    public class Farm implements IValueObject, IExternalizable
    {
        ...
        public var resources:HashCollection;
        ...
    }

And the HashCollection itself is mapped to the PersistentMap that is sent back by Hibernate:

    [RemoteClass(alias="org.hibernate.collection.PersistentMap")]
    public class HashCollection extends ArrayCollection implements IMap

On the server, everything is rather straightfoward: I have a Farm.java pojo, which contains a Map<ResourceTypeEnum, Integer> resources.

And here is the problem:
  • An enum value is stored in the data base (e.g. FOOD)
  • It is properly loaded , and the object to be sent via BlazeDS looks fine (there is a PersistentMap that contains a ResourceTypeEnum.FOOD key, with a 3 Integer as the value)
  • While debugging the message sent, I get that the FOOD key is properly sent, but not the value (FOOD = null)
  • On the client, the HashCollection that models the resources is null or empty.

Why is the value "null" in the message?

I googled a bit, and found someone who had a very similar issue. It does not work because the BlazeDS framework assumes that only String are used as keys in Maps. So it gets the key properly, then converts it to its String value, and tries and retrieves the value... which obviously fails.

So, how to get around it?
In the post linked to above, the author has created its own MapProxy to handle enums. However, this has impacts a bit everywhere in their code.
The other solutions I had at hand were:
  1. Find a smarter solution
  2. Get rid of enums
The main issue is in the BlazeDS framework itself (flex.messaging.io.amf.Amf3Output):

        if (!externalizable)
            propertyNames = proxy.getPropertyNames(instance);
       ...

        if (externalizable)
        {
           ...
        }
        else if (propertyNames != null)
        {
            Iterator it = propertyNames.iterator();
            while (it.hasNext())
            {
                String propName = (String)it.next();
                Object value = null;
                value = proxy.getValue(instance, propName);
                writeObjectProperty(propName, value);
            }
        }

Since they directly use Strings, I did not see much room for a workaround.
Given the situation, I have decided to simply not use the enums anymore (I had already gotten rid of them on the client).
So with simple Strings instead of enums, I properly get a FOOD = 3 in the message debug. However, still nothing on the client.

Mapping the server's Map to my HashCollection

Adding metadata
By default, BlazeDS maps java.util.Map objets to simple ActionScript Objects (i.e. list of property-values). Understandably, if nothing else is specified, you will get the equivalent of a ClassCastException on the client while deserializing the message - which I got.
I then added the
[RemoteClass(alias="org.hibernate.collection.PersistentMap")]
tag, hoping that it would solve the issue magically, which obviously was not the case. I did not have a ClassCastException anymore, but my HashCollection was simply empty.

Implementing flash.utils.IExternalizable
The reason why it did not work seemed obvious enough to me: the client gets a Map, which has a list of key/value pairs, and I tell the framework to map it to my HashCollection, which is not close to being the same. So adding custom serialization should do the trick.

Custom serialization is explained here. Basically, you need to implement the IExternalizable interface to do what you want with the data you receive, which I did:

        public override function readExternal(input:IDataInput):void
        {
            var map:Object = input.readObject();
            if (map != null)
            {
                for (var p:String in map)
                {
                    this.put(p, map[p]);
                }
            }
        }

        public override function writeExternal(output:IDataOutput):void
        {
            trace("write external on Flex HashCollection");
        } 

However, I never got the readExternal() piece of code to ever be called.

More googling and various experiments, it appeared that the class being sent had to implement java.io.Externalizable for the custom serialization to work.
Now, that"s more of an issue - PersistentMap is created by Hibernate, and I have no control on it. How can I have it implement Externalizable?

Specifying custom hibernate collections
Fortunately, I came accross this article that explained pretty everything I needed to know:
  1. Create a custom Map that is Externalizable
  2. Create a custom UserCollectionType to instantiate this new Map
  3. Modify the mapping file to use this new collection instead of Hibernate's default
 Here you go then with the ExternalMap:

public class ExternalMap extends PersistentMap implements Externalizable {
   ...
      public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(this.map);
      }
   }

Pretty simple - in fact, I don't want it to do anything out of the ordinary - I simply needed a map that implemented Externalizable.

The UserCollectionType is pretty much a copy-paste of the one described in the article, so I won't put it here again (you can always find it on Agricola Online google code).

Finally, the mapping for the resources have been modified

map name="resources" table="FARM_RESOURCES" cascade="all-delete-orphan" 
   lazy="false" collection-type="org.liveboardgames.common.util.ExternalMapType"

Last point to do:  update the RemoteClass tag of the HashCollection to target the new ExternalMap, and here we go!

Limitations

I have just finished this implementation. It works (the data is properly sent back from the server and populated on the client), but I have not tested the other way around yet.
Moreover, it forces me to get rid of enums, which I do not really like. So, if anyone has advices or a better solution, I'd gladly hear it :)

samedi 9 janvier 2010

BlazeDS and LazyInitializationException

I suddenly realized that even though I was successfully implementing the save of the Game entity in the data base (using hibernate), the "load" part was being completely overlooked.
When I started testing it, I had lots of LazyInitializationExceptions on the client side.

I will not go in the details of how / why this occurs - there are already several places on the net where you can find some detailed explanations, like Get Out of My POJO. There are several ways to solve this issue with BlazeDS, like integrating a third-party framework like dpHibernate (I have not used it myself, so can for now only rely on what I have read).

I really did not want to do into integrating another library, or building something myself. I thought about it, and realized that for now lazy fetching is totally useless to me for communication with the client-side. Indeed, all the data is displayed as soon as the game is loaded, so the data will be needed immediately anyway.
This may change later on, when more services are implemented server-side, but I'll guess I'll be lazy myself and check this when time comes.