Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HibernateProxyConverter - wrong serialization in case of abstract class #73

Open
DonatasC opened this issue Oct 14, 2016 · 5 comments
Open
Assignees
Labels

Comments

@DonatasC
Copy link

Hibernate allows to use entity inheritance, thus this code is valid:
private AbstractEntity myEntity = new ChildEntity();

During runtime object of ChildEntity gets proxied (by HibernateProxy). But HibernateProxyConverter currently outputs just this:

<myEntity>
... fields of ChildEntity ...
</myEntity>

Since AbstractEntity is abstract class upon deserialization I'm getting:

java.lang.InstantiationException: AbstractEntity
    at sun.misc.Unsafe.allocateInstance(Native Method) [rt.jar:1.8.0_102]
    at com.thoughtworks.xstream.converters.reflection.SunLimitedUnsafeReflectionProvider.newInstance(SunLimitedUnsafeReflectionProvider.java:76) [xstream-1.4.7.jar:1.4.7]
    at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.instantiateNewInstance(AbstractReflectionConverter.java:553) [xstream-1.4.7.jar:1.4.7]

Obviously AbstractReflectionConverter doesn't know what real class must be used and just tries to use the type of field "myEntity".
I have fixed this by modifying HibernateProxyConverter this way:

    public void marshal(final Object object, final HierarchicalStreamWriter writer, final MarshallingContext context) {
        HibernateProxy hibernateProxy = (HibernateProxy) object;
        final Object item = hibernateProxy.getHibernateLazyInitializer().getImplementation();
        // FIX goes here:
        writer.addAttribute("class", Hibernate.getClass(hibernateProxy).getName());
        context.convertAnother(item);
    }

That is, add the attribute "class" with the value of real object class. Now serialization result is:

<myEntity class="ChildEntity">
... fields of ChildEntity ...
</myEntity>

and deserialization works OK.

Could you please correct this issue with HibernateProxyConverter?

@DonatasC
Copy link
Author

Well, the issue is not with HibernateProxyConverter, but with HibernateMapper. It contains this code:

    if (HibernateProxy.class.isAssignableFrom(clazz)) {
        return super.serializedClass(clazz.getSuperclass());
    }

This is WRONG. If JPA entity inheritance is being used, clazz.getSuperclass() might return abstract class (the actual type of field, NOT of the object behind the proxy).

The CORRECT way to get the class of object behind the proxy is:

Hibernate.getClass(proxy)

or

( (HibernateProxy) proxy ).getHibernateLazyInitializer().getImplementation().getClass()

But here we hit XStream internal design issue: Mappers do not have access to objects being serialized, only to their classes. Any ideas?

@joehni joehni added the bug label Oct 19, 2016
@joehni joehni self-assigned this Oct 19, 2016
@joehni
Copy link
Member

joehni commented Oct 19, 2016

Beware: The Hibernate module was completely contributed by other users, personally I never did something with it.

You're last comment is right: You do not have access to the real object in the mapper. Therefore I'd implement something along your first solution. The class attribute will overwrite any other type here.

@DonatasC
Copy link
Author

Well I have tried to add following line to HibernateProxyConverter:

writer.addAttribute("class", Hibernate.getClass(hibernateProxy).getName());

but then as a result I get "Duplicated attribute error". It seems that sometimes parent converter (AbstractReflectionConverter) adds this attribute itself.

Question: are there any means to ask HierarchicalStreamWriter to overwrite the attribute?

@joehni
Copy link
Member

joehni commented Oct 24, 2016

Well, normally the task of a converter is to represent the current object in XML. This principle is violated by the HibernateProxyConverter on purpose to remove any trace of the proxy object from the XML representation. Normally the marshal method should be implemented like:

public void marshal(final Object object, final HierarchicalStreamWriter writer, final MarshallingContext context) {
    HibernateProxy hibernateProxy = (HibernateProxy) object;
    final Object item = hibernateProxy.getHibernateLazyInitializer().getImplementation();
    writer.startNode("hibernate-proxy");
    writer.addAttribute("class", Hibernate.getClass(hibernateProxy).getName());
    context.convertAnother(item);
    writer.endNode();
}

Since the converter does not represent its own object (on purpose), you get this problem with the double class attribute.

The so-called parent converter simply detects that the declared type of the field does not match the real type of the object and writes therefore the proper class attribute (since it has to keep all information to recreate the elements ... normally). I'd expect that you get this always when you've declared the member to be of type AbstractEntity. Is that the case?

@joehni
Copy link
Member

joehni commented Nov 9, 2016

I cannot go further without more info or a little test case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants