Friday, March 21, 2008

Xstream with Hibernate

People have been asking in the comments to my post on XStream about how to properly integrate Hibernate with XStream, as there are few pieces of puzzle that don't necessarily fit together perfectly. I answered in the comments, but Blogger annoyingly doesn't allow a lot of formatting in comments, so I decided to write a separate follow-up post; here it goes.

  • First of all, I use Hibernate 2. I know, it's old, but it does the work and I won't fix it unless it is broken. I expect most of the advice will also apply to Hibernate 3.

  • I'm aliasing all classes using XStream.alias(). Reason being that on the server side, some data model classes are actually subclassed for additional (although non-Hibernate) related functionality. You might or might not need this.

  • This is however essential: We need to ensure that XStream will treat Hibernate lists, sets, and maps as Java lists, sets, and maps. I believe this is what helps avoid the infamous "com.thoughtworks.xstream.converters.ConversionException: Cannot handle CGLIB enhanced proxies with multiple callbacks..." problem. There are three things that need to be done:
    • use Xstream.addDefaultImplementation() to tell XStream to treat all Hibernate-enhanced collection classes as plain Java collections:
      xstream.addDefaultImplementation(
      net.sf.hibernate.collection.List.class, java.util.List.class);
      xstream.addDefaultImplementation(
      net.sf.hibernate.collection.Map.class, java.util.Map.class);
      xstream.addDefaultImplementation(
      net.sf.hibernate.collection.Set.class, java.util.Set.class);

    • Finally, in order for XStream to actually handle these collections I needed to define and register some custom converters that are able to handle Hibernate collections as Java collections:
      Mapper mapper = xstream.getMapper();
      xstream.registerConverter(new HibernateCollectionConverter(mapper));
      xstream.registerConverter(new HibernateMapConverter(mapper));
      These custom converter classes are rather trivial, here are their definitions. All they really do is extend the XStream built-in collection and map converters, and declare their ability to handle Hibernate lists, sets, and maps:
      import net.sf.hibernate.collection.List;
      import net.sf.hibernate.collection.Set;
      import com.thoughtworks.xstream.converters.collections.CollectionConverter;
      import com.thoughtworks.xstream.mapper.Mapper;

      class HibernateCollectionConverter extends CollectionConverter {
      HibernateCollectionConverter(Mapper mapper) {
      super(mapper);
      }

      public boolean canConvert(Class type) {
      return super.canConvert(type) || type == List.class || type == Set.class;
      }
      }
      and
      import net.sf.hibernate.collection.Map;
      import com.thoughtworks.xstream.converters.collections.MapConverter;
      import com.thoughtworks.xstream.mapper.Mapper;

      class HibernateMapConverter extends MapConverter {

      HibernateMapConverter(Mapper mapper) {
      super(mapper);
      }

      public boolean canConvert(Class type) {
      return super.canConvert(type) || type == Map.class;
      }
      }


That's all I did and it eliminated all of my Hibernate+XStream problems - hope it will also help you.

12 comments:

Peacemaker said...

So is this with Hibernate 2 or Hibernate 3? I am using Hibernate 3 and i tried using the above solution and it does not work.

The other weird thing is the same stuff works (XML conversion) on the same Object (same hibernate mapping) on a different scenario.

Wondering whats wrong??? Any inputs on this would be greatly appreciated

Cheers & Thanks
Peacemaker

Attila Szegedi said...

I was using Hibernate 2 -- it could well be that it doesn't work this way with Hibernate 3. If anyone has a H3 solution, you're welcome to post it here.

sweetfa said...

Hibernate 3 is simply a matter of replacing net.sf.hibernate.collection with org.hibernate.mapping in each of the files.

sweetfa said...

Another issue related to hibernate3 and xstream is that the CGLIB proxy generated by hibernate actually contains a null second value. I have created a fix converter class based on the CGLIBEnhancedConverter class.

This can be downloaded from http://bushlife.com.au/downloads/xstream/CGLIBEnhancedConverterHibernateFix.java

This is relevant for at least XStream 1.2.2 and XStream 1.3.

This needs to be registered via xstream.registerConverter(new CGLIBEnhancedConverterHibernateFix(mapper,xstream.getReflectionProvider()));

For more information see http://jira.codehaus.org/browse/XSTR-423

Anonymous said...

Greate Sweetfa!!

Your solution is work.

However, the addDefaultImplement with org.hibernate.mapping.List, Map and Set are not work.

xstream.addDefaultImplementation(org.hibernate.mapping.List.class, java.util.List.class);
xstream.addDefaultImplementation(org.hibernate.mapping.Map.class, java.util.Map.class);
xstream.addDefaultImplementation(org.hibernate.mapping.Set.class, java.util.Set.class);
Mapper mapper = xstream.getMapper();
xstream.registerConverter(new HibernateCollectionConverter(mapper));
xstream.registerConverter(new HibernateMapConverter(mapper));

I got this error during runtime.
com.thoughtworks.xstream.converters.ConversionException: Cannot instantiate org.hibernate.mapping.Set

Anyway, only these line is enough for me.
Mapper mapper = xstream.getMapper();
xstream.registerConverter(new CGLIBEnhancedConverterHibernateFix(mapper,xstream.getReflectionProvider()));

maximiert said...

THANK YOU ALL for this!

Tim said...

Hibernate 3 support that worked in my situation:

XStream xstream = new XStream();
xstream.addDefaultImplementation(java.util.ArrayList.class, org.hibernate.collection.PersistentList.class); xstream.addDefaultImplementation(java.util.HashMap.class, org.hibernate.collection.PersistentMap.class); xstream.addDefaultImplementation(java.util.HashSet.class, org.hibernate.collection.PersistentSet.class);

Mapper mapper = xstream.getMapper();
xstream.registerConverter(new HibernateCollectionConverter(mapper));
xstream.registerConverter(new HibernateMapConverter(mapper));


class HibernateCollectionConverter extends
CollectionConverter {
HibernateCollectionConverter(Mapper mapper) {
super(mapper);
}

public boolean canConvert(Class type) {
return super.canConvert(type)
|| org.hibernate.collection.PersistentList.class
.equals(type)
|| org.hibernate.collection.PersistentSet.class
.equals(type);
}
}

class HibernateMapConverter extends MapConverter {

HibernateMapConverter(Mapper mapper) {
super(mapper);
}

public boolean canConvert(Class type) {
return super.canConvert(type)
|| org.hibernate.collection.PersistentMap.class
.equals(type);
}
}

Anonymous said...

This was a life saving tip. Thanks so much!

Anonymous said...

Tims solution worked for me. However, I also had to add a check for org.hibernate.collection.PersistentBag on HibernateCollectionConverter.canConvert(Class type).
Best regards,
Daniel Felix Ferber

Yvon said...

Very thank you Tim.
Nice tips!

Anonymous said...

xStream.addDefaultImplementation(org.hibernate.mapping.Set.class, java.util.List.class);
xStream.addDefaultImplementation(org.hibernate.mapping.List.class, java.util.Set.class);
xStream.addDefaultImplementation(org.hibernate.mapping.Map.class, java.util.Map.class);
xStream.addDefaultImplementation(org.hibernate.collection.PersistentSet.class, java.util.Set.class);

Anonymous said...

Thanks a lot :-)