XStream

support for Hibernate collections

Details

  • Type: New Feature New Feature
  • Status: Closed Closed
  • Resolution: Fixed
  • Affects Version/s: None
  • Fix Version/s: 1.4
  • Component/s: None
  • Labels:
    None

Description

A converter/mapper for supporting hibernate collections.

  1. HibernateCollectionConverter.java
    14/Jun/05 7:11 AM
    4 kB
    Costin Leau
  2. HibernateCollectionConverterNEW.java
    09/Oct/09 1:29 AM
    4 kB
    Erich S.
  3. HibernateCollectionsMapper.java
    14/Jun/05 7:11 AM
    3 kB
    Costin Leau
  4. HibernateCollectionsMapperNEW.java
    09/Oct/09 1:29 AM
    4 kB
    Erich S.
  5. HibernateMapper.java
    24/Jan/11 5:51 AM
    1 kB
    Tim De Meyer
  6. HibernateMapperNEW.java
    09/Oct/09 1:29 AM
    2 kB
    Erich S.
  7. HibernateProxyConverterNEW.java
    09/Oct/09 1:29 AM
    1 kB
    Erich S.
  8. HibernateProxyConverterNEW2.java
    29/Aug/10 7:42 PM
    1 kB
    Jaime Metcher
  9. HibernateProxyXPathMarshaller.java
    07/Sep/10 4:39 PM
    1.0 kB
    Jaime Metcher
  10. xstream_hib.tar.gz
    08/Apr/11 1:15 AM
    24 kB
    Jaime Metcher
  11. XStreamMarshallingStrategy.java
    07/Sep/10 4:39 PM
    1.0 kB
    Jaime Metcher

Issue Links

Activity

Hide
Costin Leau added a comment -

converter

Show
Costin Leau added a comment - converter
Hide
Costin Leau added a comment -

mapper

Show
Costin Leau added a comment - mapper
Hide
Donald Ball added a comment -

I found this more comprehensive solution only after I'd written my own. I'd love for this to be included in the main xstream distro, it's probably going to be a very common use case.

Question while I'm at it - anyone done any work on handling proxy classes decorated by cglib by hibernate? The simple solution would be to simply disable them, but lazy loading of fields is occasionally a big big win, so I'm reluctant to go down that road.

Show
Donald Ball added a comment - I found this more comprehensive solution only after I'd written my own. I'd love for this to be included in the main xstream distro, it's probably going to be a very common use case. Question while I'm at it - anyone done any work on handling proxy classes decorated by cglib by hibernate? The simple solution would be to simply disable them, but lazy loading of fields is occasionally a big big win, so I'm reluctant to go down that road.
Hide
Costin Leau added a comment -

I've seen your comment only now - I didn't had any watches placed on. I'm glad you like the solution.
I have done some work on decorated classes some time ago and I can donate some code if there is such a need. If you are in need of a quick fix make a mapper that removes the signature of CGLIB.

Show
Costin Leau added a comment - I've seen your comment only now - I didn't had any watches placed on. I'm glad you like the solution. I have done some work on decorated classes some time ago and I can donate some code if there is such a need. If you are in need of a quick fix make a mapper that removes the signature of CGLIB.
Hide
Costin Leau added a comment -

After struggling with the HB api I stopped working on the tests. PersistentCollections & Co. are highly connected to HB API and to me it seems almost impossible (you have to create the whole internal HB hierarchy with dummy objects that in the end do not work).

Show
Costin Leau added a comment - After struggling with the HB api I stopped working on the tests. PersistentCollections & Co. are highly connected to HB API and to me it seems almost impossible (you have to create the whole internal HB hierarchy with dummy objects that in the end do not work).
Hide
Costin Leau added a comment -

I've added a mapper for CGLIB enhanced classes: http://jira.codehaus.org/browse/XSTR-238

Show
Costin Leau added a comment - I've added a mapper for CGLIB enhanced classes: http://jira.codehaus.org/browse/XSTR-238
Hide
David Hay added a comment -

Also see http://forum.hibernate.org/viewtopic.php?t=939007&highlight=xstream for apparent error in the code (have checked it myself).

Show
David Hay added a comment - Also see http://forum.hibernate.org/viewtopic.php?t=939007&highlight=xstream for apparent error in the code (have checked it myself).
Hide
Joerg Schaible added a comment -

I just reopen the issue, since the HIbernate problem is not solved, but asked at a constant rate. So the issue will not get lost, even if the donated code might not solve any issue.

Show
Joerg Schaible added a comment - I just reopen the issue, since the HIbernate problem is not solved, but asked at a constant rate. So the issue will not get lost, even if the donated code might not solve any issue.
Hide
Hector U. Velez added a comment -

The following line of code might be incorrect in the HibernateCollectionConverter

// the set is returned as a map by Hibernate (unclear why exactly)
if (source instanceof PersistentSet)

{ collection = new HashSet(((HashMap)collection).entrySet()); }

((HashMap)collection).entrySet() will return a set of Map.Entry and not the value which I think is what we want.

Maybe it should be something like:
if (source instanceof PersistentSet)
{
Set newSet = new HashSet();
Set mapEntrySet = ((HashMap) collection).entrySet();
for (Object o : mapEntrySet)

{ Map.Entry entry = (Map.Entry)o; newSet.add(entry.getValue()); }

}

Show
Hector U. Velez added a comment - The following line of code might be incorrect in the HibernateCollectionConverter // the set is returned as a map by Hibernate (unclear why exactly) if (source instanceof PersistentSet) { collection = new HashSet(((HashMap)collection).entrySet()); } ((HashMap)collection).entrySet() will return a set of Map.Entry and not the value which I think is what we want. Maybe it should be something like: if (source instanceof PersistentSet) { Set newSet = new HashSet(); Set mapEntrySet = ((HashMap) collection).entrySet(); for (Object o : mapEntrySet) { Map.Entry entry = (Map.Entry)o; newSet.add(entry.getValue()); } }
Hide
Eric Raymond added a comment -

Another approach here is to integrate functionality provided by beanlib to make this problem go away.

http://beanlib.sourceforge.net/

Hibernate3BeanReplicator().deepCopy() handles two issues with hibernate:

  • It de-proxies all the (lazy) objects
  • It replaces hibernate specific collection types with the Java built in versions.

Of course this deep copy is not very efficient if you do it in addition to XStream's traversal of the object. Ideally this would be handled internally as XStream is traversing.

Show
Eric Raymond added a comment - Another approach here is to integrate functionality provided by beanlib to make this problem go away. http://beanlib.sourceforge.net/ Hibernate3BeanReplicator().deepCopy() handles two issues with hibernate:
  • It de-proxies all the (lazy) objects
  • It replaces hibernate specific collection types with the Java built in versions.
Of course this deep copy is not very efficient if you do it in addition to XStream's traversal of the object. Ideally this would be handled internally as XStream is traversing.
Hide
Shahiduzzaman added a comment -

Other than converting the Hibernate collection type as Java standard collection types, is there any other benefit of this converter (And why should one need to do that) ? If I'm not wrong, It does not facilitate serializing hibernate lazy loaded classes.

Show
Shahiduzzaman added a comment - Other than converting the Hibernate collection type as Java standard collection types, is there any other benefit of this converter (And why should one need to do that) ? If I'm not wrong, It does not facilitate serializing hibernate lazy loaded classes.
Hide
Erich S. added a comment -

Hello all,

on the work of Costin and others we improved some things to get Hibernate 3 objects (incl. Lazy-Loading) sererialised - and back again. With the following solution we get succeeded:

public String exportToXML(Object obj) {
if (obj == null)
return null;

XStream xstream = new XStream();
final ClassMapper cm = xstream.getClassMapper();

xstream = new XStream() {
protected MapperWrapper wrapMapper(MapperWrapper next) { return new HibernateMapperNEW(next); }

protected Mapper buildMapper() { return new HibernateCollectionsMapperNEW(cm); }
};

xstream.registerConverter(new HibernateCollectionConverterNEW(xstream
.getConverterLookup()));

xstream.registerConverter(new HibernateProxyConverterNEW(xstream
.getMapper(), new PureJavaReflectionProvider()),
XStream.PRIORITY_VERY_HIGH);

return xstream.toXML(obj);
}

public Object importXML(String xmlString) { if (xmlString == null || xmlString.length() <= 0) return null; /* * No Hibernate specific things to do: simple POJOS are to be importet */ XStream xstream = new XStream(); return xstream.fromXML(xmlString); }

Show
Erich S. added a comment - Hello all, on the work of Costin and others we improved some things to get Hibernate 3 objects (incl. Lazy-Loading) sererialised - and back again. With the following solution we get succeeded: public String exportToXML(Object obj) { if (obj == null) return null; XStream xstream = new XStream(); final ClassMapper cm = xstream.getClassMapper(); xstream = new XStream() { protected MapperWrapper wrapMapper(MapperWrapper next) { return new HibernateMapperNEW(next); } protected Mapper buildMapper() { return new HibernateCollectionsMapperNEW(cm); } }; xstream.registerConverter(new HibernateCollectionConverterNEW(xstream .getConverterLookup())); xstream.registerConverter(new HibernateProxyConverterNEW(xstream .getMapper(), new PureJavaReflectionProvider()), XStream.PRIORITY_VERY_HIGH); return xstream.toXML(obj); } public Object importXML(String xmlString) { if (xmlString == null || xmlString.length() <= 0) return null; /* * No Hibernate specific things to do: simple POJOS are to be importet */ XStream xstream = new XStream(); return xstream.fromXML(xmlString); }
Hide
Erich S. added a comment - - edited

Files with Suffix ...NEW are for my suggested solution

Show
Erich S. added a comment - - edited Files with Suffix ...NEW are for my suggested solution
Hide
Erich S. added a comment - - edited

Files with Suffix ...NEW are for my suggested solution

Show
Erich S. added a comment - - edited Files with Suffix ...NEW are for my suggested solution
Hide
Carlos Puente added a comment -

This last solution works well directly with hibernate objects but if you use something like List<HibernateObject> (or HibernateObject[] ...) after the first object in the group it fails to dereference deep relations. Anyway you could parse the objects one by one.

It also uses deprecated Classes but it is easy to fix using:
Mapper instead of ClassMapper
AbstractXmlFriendlyMapper instead of XmlFriendlyMapper
escapeFieldName(replaceClasses(javaName)) instead of super.mapNameToXML(replaceClasses(javaName))

Show
Carlos Puente added a comment - This last solution works well directly with hibernate objects but if you use something like List<HibernateObject> (or HibernateObject[] ...) after the first object in the group it fails to dereference deep relations. Anyway you could parse the objects one by one. It also uses deprecated Classes but it is easy to fix using: Mapper instead of ClassMapper AbstractXmlFriendlyMapper instead of XmlFriendlyMapper escapeFieldName(replaceClasses(javaName)) instead of super.mapNameToXML(replaceClasses(javaName))
Hide
Jake added a comment -

I've found an issue though: it doesn't seem to stream objects that were added to the list after hibernate has persisted the list.
For example:
Person person = dao.getPerson(someId);
person.addFriend(friend);
xstream.toXML(person);

The new friend won't show.

Show
Jake added a comment - I've found an issue though: it doesn't seem to stream objects that were added to the list after hibernate has persisted the list. For example: Person person = dao.getPerson(someId); person.addFriend(friend); xstream.toXML(person); The new friend won't show.
Hide
Jaime Metcher added a comment -

Just a minor tweak to HibernateProxyConverterNEW to make it look up a converter for the proxied class, rather than just call super.marshal(). See HibernateProxyConverterNEW2

Without this you just get basic reflection converter behaviour, meaning any registered converters are ignored. It's a little worse than that, actually, as registered converters may or may not be invoked depending on whether you happen to have a proxied or non-proxied instance at the time.

Show
Jaime Metcher added a comment - Just a minor tweak to HibernateProxyConverterNEW to make it look up a converter for the proxied class, rather than just call super.marshal(). See HibernateProxyConverterNEW2 Without this you just get basic reflection converter behaviour, meaning any registered converters are ignored. It's a little worse than that, actually, as registered converters may or may not be invoked depending on whether you happen to have a proxied or non-proxied instance at the time.
Hide
Jaime Metcher added a comment -

Actually, the HibernateProxyConverterNEW2 approach still doesn't cut it. It fails to cope with the scenario where the one object appears in the object graph in both proxied and unproxied form. It doesn't recognise them as the same object, so you get two instances in the XML.

See the attached XStreamMarshallingStrategy and HibernateProxyXPathMarshaller for a possible solution. Then you just need:

xstream.setMarshallingStrategy(new XStreamMarshallingStrategy(XStreamMarshallingStrategy.RELATIVE));

Note that this strategy is only required for toXML, not fromXML.

Show
Jaime Metcher added a comment - Actually, the HibernateProxyConverterNEW2 approach still doesn't cut it. It fails to cope with the scenario where the one object appears in the object graph in both proxied and unproxied form. It doesn't recognise them as the same object, so you get two instances in the XML. See the attached XStreamMarshallingStrategy and HibernateProxyXPathMarshaller for a possible solution. Then you just need: xstream.setMarshallingStrategy(new XStreamMarshallingStrategy(XStreamMarshallingStrategy.RELATIVE)); Note that this strategy is only required for toXML, not fromXML.
Hide
David Wroton added a comment -

Does anyone have an example of using this and which files are needed. I am unclear from reading the comments what I actually need to implement in order to try this.

Show
David Wroton added a comment - Does anyone have an example of using this and which files are needed. I am unclear from reading the comments what I actually need to implement in order to try this.
Hide
Jaime Metcher added a comment -

@David,

Start with the attachements added by Erich S. and look at his comment from 9/Oct/09 to see how to use them. Then, if you care about the issues I mentioned you can have a look at the changes I made. All you need to do is use HibernateProxyConverterNEW2 instead of HibernateProxyConverterNEW, and add the line from my previous comment straight after you instanstiate xstream.

Show
Jaime Metcher added a comment - @David, Start with the attachements added by Erich S. and look at his comment from 9/Oct/09 to see how to use them. Then, if you care about the issues I mentioned you can have a look at the changes I made. All you need to do is use HibernateProxyConverterNEW2 instead of HibernateProxyConverterNEW, and add the line from my previous comment straight after you instanstiate xstream.
Hide
Kevin McGlynn added a comment - - edited

This code is working like a charm for me.

My only problem is that text data (string columns defined as LOB in hibernate annotations... CLOB in oracle) are not appearing. Any ideas/pointers on how to implement a mapping on this type of column?

Show
Kevin McGlynn added a comment - - edited This code is working like a charm for me. My only problem is that text data (string columns defined as LOB in hibernate annotations... CLOB in oracle) are not appearing. Any ideas/pointers on how to implement a mapping on this type of column?
Hide
Tim De Meyer added a comment - - edited

Guys,

any idea on how to make class aliasing work for lazy loaded objects?

We want to alias my.package.MyEntity to my.other.package.MyOtherEntity.

We do this by calling xStream.aliasType() or xStream.alias().

However, it doesn't do the trick for lazy loaded objects (appears as MyEntity_$$_javassist_42 in eclipse debugger)

XStream initialization is based on code from Erich S. earlier in this thread.

Show
Tim De Meyer added a comment - - edited Guys, any idea on how to make class aliasing work for lazy loaded objects? We want to alias my.package.MyEntity to my.other.package.MyOtherEntity. We do this by calling xStream.aliasType() or xStream.alias(). However, it doesn't do the trick for lazy loaded objects (appears as MyEntity_$$_javassist_42 in eclipse debugger) XStream initialization is based on code from Erich S. earlier in this thread.
Hide
Tim De Meyer added a comment - - edited

Hi,

I found out that the culprit is actually Erich's HibernateMapper.
As you can see in the implementation, the Mapper chain gets broken by directly returning a value, instead of handing it off to the next Mapper in the chain.
Therefore XStreams ClassAliasingMapper, which comes later in the chain, doesn't get a chance to do any aliasing.
I'll attach the fix in a few minutes.

By the way, Costin, Erich and Jaime, I'll take this opportunity to thank you guys for the contributions you've made.
This has really been a life saver!

t.

Show
Tim De Meyer added a comment - - edited Hi, I found out that the culprit is actually Erich's HibernateMapper. As you can see in the implementation, the Mapper chain gets broken by directly returning a value, instead of handing it off to the next Mapper in the chain. Therefore XStreams ClassAliasingMapper, which comes later in the chain, doesn't get a chance to do any aliasing. I'll attach the fix in a few minutes. By the way, Costin, Erich and Jaime, I'll take this opportunity to thank you guys for the contributions you've made. This has really been a life saver! t.
Hide
Joerg Schaible added a comment -

Guys, normally I stop conversation like this in JIRA, because it is a bug tracker and not a help forum. However, I admin that the Hibernate problem is a delicate and a long standing issue and is a somewhat special case.

What is the basic problem of not adding this support until now:
1/ XStream is currently delivered as a single library and Hibernate adds a lot of new dependencies
2/ No Hibernate knowledge on our side

To address the first topic, we'd add Hibernate support as separate artifact that delivers all stuff necessary for the integration. However, the second problem is more serious. If it is officially delivered with XStream, we have to maintain it. While you all have put quite some effort into this integration, nobody delivered unit tests so far. To enable maintenace from our side this is an absolute requirement. The unit tests have to cover the typical Hibernate scenarios (incl. collections and lazy resp. non-lazy loading). If a pom is provided on top the chance is very high to get this into XStream 1.4.

Show
Joerg Schaible added a comment - Guys, normally I stop conversation like this in JIRA, because it is a bug tracker and not a help forum. However, I admin that the Hibernate problem is a delicate and a long standing issue and is a somewhat special case. What is the basic problem of not adding this support until now: 1/ XStream is currently delivered as a single library and Hibernate adds a lot of new dependencies 2/ No Hibernate knowledge on our side To address the first topic, we'd add Hibernate support as separate artifact that delivers all stuff necessary for the integration. However, the second problem is more serious. If it is officially delivered with XStream, we have to maintain it. While you all have put quite some effort into this integration, nobody delivered unit tests so far. To enable maintenace from our side this is an absolute requirement. The unit tests have to cover the typical Hibernate scenarios (incl. collections and lazy resp. non-lazy loading). If a pom is provided on top the chance is very high to get this into XStream 1.4.
Hide
nicolas lescure added a comment -

Hi all,
Thanks all for this great solution !
I was playing around with this fix and I was having the classic hibernate exceptions telling there is no session attached any more.
The culprit was in HibernateCollectionConverterNEW.java when at the beginning of the marshal method, col.forceInitialization() is called.
I added a simple fix which will write null if the collection is null and won't call col.forceInitialization() is the session is null or not connected:
if (source instanceof AbstractPersistentCollection) {
AbstractPersistentCollection col = (AbstractPersistentCollection) source;
if ((col.getSession() != null) && col.getSession().isConnected()) { col.forceInitialization(); }
collection = col.getStoredSnapshot();
} else if (source instanceof PersistentCollection) { PersistentCollection col = (PersistentCollection) source; col.forceInitialization(); // ToDo ES: collection = col.getCollectionSnapshot().getSnapshot(); collection = col.getStoredSnapshot(); }

if (collection == null) { NULL_CONVERTER.marshal(collection, writer, context); return; }

...

Show
nicolas lescure added a comment - Hi all, Thanks all for this great solution ! I was playing around with this fix and I was having the classic hibernate exceptions telling there is no session attached any more. The culprit was in HibernateCollectionConverterNEW.java when at the beginning of the marshal method, col.forceInitialization() is called. I added a simple fix which will write null if the collection is null and won't call col.forceInitialization() is the session is null or not connected: if (source instanceof AbstractPersistentCollection) { AbstractPersistentCollection col = (AbstractPersistentCollection) source; if ((col.getSession() != null) && col.getSession().isConnected()) { col.forceInitialization(); } collection = col.getStoredSnapshot(); } else if (source instanceof PersistentCollection) { PersistentCollection col = (PersistentCollection) source; col.forceInitialization(); // ToDo ES: collection = col.getCollectionSnapshot().getSnapshot(); collection = col.getStoredSnapshot(); } if (collection == null) { NULL_CONVERTER.marshal(collection, writer, context); return; } ...
Hide
Jaime Metcher added a comment - - edited

xstream_hib.tar.gz (new version 29/Mar) is a tarball containing a maven/eclipse project bringing together some of the code people have been contributing to this issue, along with some minimal unit tests. The eclipse project is not a standalone alternative to maven - it relies on all the dependencies being in the maven repo.

This is intended to be a starting point so that future tweaks have a ready-made place to add unit tests. I have not come within light years of testing all of XStream's functionality in a Hibernate context, or within megaparsecs of testing all of Hibernate in an XStream context.

In particular:

I haven't addressed Jake's concerns about unflushed changes not being serialized.
I haven't done anything with LOB's (sorry Kevin).
I have included a test for Tim's aliasing concerns, but I removed a lot of Tim's changes because I didn't understand what they did and they weren't needed for that specific test.
I wasn't able to reproduce my own issues with the same object appearing in both proxied and unproxied form.
I haven't included Nicolas' code for serializing detached objects.

More background on the dev mailing list.

Show
Jaime Metcher added a comment - - edited xstream_hib.tar.gz (new version 29/Mar) is a tarball containing a maven/eclipse project bringing together some of the code people have been contributing to this issue, along with some minimal unit tests. The eclipse project is not a standalone alternative to maven - it relies on all the dependencies being in the maven repo. This is intended to be a starting point so that future tweaks have a ready-made place to add unit tests. I have not come within light years of testing all of XStream's functionality in a Hibernate context, or within megaparsecs of testing all of Hibernate in an XStream context. In particular: I haven't addressed Jake's concerns about unflushed changes not being serialized. I haven't done anything with LOB's (sorry Kevin). I have included a test for Tim's aliasing concerns, but I removed a lot of Tim's changes because I didn't understand what they did and they weren't needed for that specific test. I wasn't able to reproduce my own issues with the same object appearing in both proxied and unproxied form. I haven't included Nicolas' code for serializing detached objects. More background on the dev mailing list.
Hide
Joerg Schaible added a comment -

Hi Jaime,

can you provide a version where the copyright in the sources is hold by the "XStream committers"? Simply add your name with an author tag to the classes instead.

Thanks!

Show
Joerg Schaible added a comment - Hi Jaime, can you provide a version where the copyright in the sources is hold by the "XStream committers"? Simply add your name with an author tag to the classes instead. Thanks!
Hide
Jaime Metcher added a comment -

New version of xstream_hib.tar.gz with amended copyright notices

Show
Jaime Metcher added a comment - New version of xstream_hib.tar.gz with amended copyright notices
Hide
Joerg Schaible added a comment -

Support is now in trunk in separate module. Thanks to all contributions and remarks.

Show
Joerg Schaible added a comment - Support is now in trunk in separate module. Thanks to all contributions and remarks.

People

Vote (10)
Watch (18)

Dates

  • Created:
    Updated:
    Resolved: