yasson icon indicating copy to clipboard operation
yasson copied to clipboard

Error serializing HibernateProxy properties (member variables)

Open pedroosorio opened this issue 5 years ago • 8 comments
trafficstars

Hi !

I'm trying to serialize an object that has hibernate proxies in it. Some of those proxies also contain other members that are proxies.

I can confirm that the LazyInitializer.getImplementation() returns a valid unproxied object in all instances.

Once i try to serialize the object, i get the following exception:

Caused by: javax.json.bind.JsonbException: Unable to serialize property 'pricePlanCurrent' from com.smithmicro.safepath.family.persistence.entity.Account
	at org.eclipse.yasson//org.eclipse.yasson.internal.serializer.ObjectSerializer.serializeInternal(ObjectSerializer.java:67)
	at org.eclipse.yasson//org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serialize(AbstractContainerSerializer.java:64)
	at org.eclipse.yasson//org.eclipse.yasson.internal.Marshaller.serializeRoot(Marshaller.java:148)
	at org.eclipse.yasson//org.eclipse.yasson.internal.Marshaller.marshall(Marshaller.java:76)
	at org.eclipse.yasson//org.eclipse.yasson.internal.Marshaller.marshall(Marshaller.java:102)
	at org.eclipse.yasson//org.eclipse.yasson.internal.JsonBinding.toJson(JsonBinding.java:118)
	...
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at [email protected]//org.jboss.as.ee.component.ManagedReferenceMethodInterceptor.processInvocation(ManagedReferenceMethodInterceptor.java:52)
	at [email protected]//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
	at [email protected]//org.jboss.invocation.InterceptorContext$Invocation.proceed(InterceptorContext.java:509)
	at [email protected]//org.jboss.as.weld.interceptors.Jsr299BindingsInterceptor.delegateInterception(Jsr299BindingsInterceptor.java:79)
	at [email protected]//org.jboss.as.weld.interceptors.Jsr299BindingsInterceptor.doMethodInterception(Jsr299BindingsInterceptor.java:89)
	at [email protected]//org.jboss.as.weld.interceptors.Jsr299BindingsInterceptor.processInvocation(Jsr299BindingsInterceptor.java:102)
	at [email protected]//org.jboss.as.ee.component.interceptors.UserInterceptorFactory$1.processInvocation(UserInterceptorFactory.java:63)
	at [email protected]//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
	at [email protected]//org.jboss.as.ejb3.component.invocationmetrics.ExecutionTimeInterceptor.processInvocation(ExecutionTimeInterceptor.java:43)
	at [email protected]//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
	at [email protected]//org.jboss.as.jpa.interceptor.SBInvocationInterceptor.processInvocation(SBInvocationInterceptor.java:47)
	at [email protected]//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
	at [email protected]//org.jboss.as.ee.concurrent.ConcurrentContextInterceptor.processInvocation(ConcurrentContextInterceptor.java:45)
	at [email protected]//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
	at [email protected]//org.jboss.invocation.InitialInterceptor.processInvocation(InitialInterceptor.java:40)
	at [email protected]//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
	at [email protected]//org.jboss.invocation.ChainedInterceptor.processInvocation(ChainedInterceptor.java:53)
	at [email protected]//org.jboss.as.ee.component.interceptors.ComponentDispatcherInterceptor.processInvocation(ComponentDispatcherInterceptor.java:52)
	at [email protected]//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
	at [email protected]//org.jboss.as.ejb3.component.pool.PooledInstanceInterceptor.processInvocation(PooledInstanceInterceptor.java:51)
	at [email protected]//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
	at [email protected]//org.jboss.as.ejb3.component.interceptors.AdditionalSetupInterceptor.processInvocation(AdditionalSetupInterceptor.java:54)
	at [email protected]//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
	at [email protected]//org.jboss.as.ejb3.tx.CMTTxInterceptor.invokeInNoTx(CMTTxInterceptor.java:216)
	... 173 more
Caused by: javax.json.bind.JsonbException: Unable to serialize property 'hibernateLazyInitializer' from com.smithmicro.safepath.family.persistence.entity.PricePlan$HibernateProxy$Sne6Togw
	at org.eclipse.yasson//org.eclipse.yasson.internal.serializer.ObjectSerializer.serializeInternal(ObjectSerializer.java:67)
	at org.eclipse.yasson//org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serialize(AbstractContainerSerializer.java:64)
	at org.eclipse.yasson//org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serializerCaptor(AbstractContainerSerializer.java:96)
	at org.eclipse.yasson//org.eclipse.yasson.internal.serializer.ObjectSerializer.marshallProperty(ObjectSerializer.java:110)
	at org.eclipse.yasson//org.eclipse.yasson.internal.serializer.ObjectSerializer.serializeInternal(ObjectSerializer.java:65)
	... 209 more
Caused by: javax.json.bind.JsonbException: Unable to serialize property 'readOnly' from org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor
	at org.eclipse.yasson//org.eclipse.yasson.internal.serializer.ObjectSerializer.serializeInternal(ObjectSerializer.java:67)
	at org.eclipse.yasson//org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serialize(AbstractContainerSerializer.java:64)
	at org.eclipse.yasson//org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serializerCaptor(AbstractContainerSerializer.java:96)
	at org.eclipse.yasson//org.eclipse.yasson.internal.serializer.ObjectSerializer.marshallProperty(ObjectSerializer.java:110)
	at org.eclipse.yasson//org.eclipse.yasson.internal.serializer.ObjectSerializer.serializeInternal(ObjectSerializer.java:65)
	... 213 more
Caused by: javax.json.bind.JsonbException: Error getting value on: org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor@2a176e6e
	at org.eclipse.yasson//org.eclipse.yasson.internal.model.GetValueCommand.getValue(GetValueCommand.java:36)
	at org.eclipse.yasson//org.eclipse.yasson.internal.model.ReflectionPropagation.getValue(ReflectionPropagation.java:73)
	at org.eclipse.yasson//org.eclipse.yasson.internal.model.PropertyModel.getValue(PropertyModel.java:284)
	at org.eclipse.yasson//org.eclipse.yasson.internal.serializer.ObjectSerializer.marshallProperty(ObjectSerializer.java:87)
	at org.eclipse.yasson//org.eclipse.yasson.internal.serializer.ObjectSerializer.serializeInternal(ObjectSerializer.java:65)
	... 217 more
Caused by: java.lang.reflect.InvocationTargetException
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.eclipse.yasson//org.eclipse.yasson.internal.model.GetFromGetter.internalGetValue(GetFromGetter.java:28)
	at org.eclipse.yasson//org.eclipse.yasson.internal.model.GetValueCommand.getValue(GetValueCommand.java:34)
	... 221 more
Caused by: org.hibernate.TransientObjectException: Proxy [com.smithmicro.safepath.family.persistence.entity.PricePlan#3] is detached (i.e, session is null). The read-only/modifiable setting is only accessible when the proxy is associated with an open session.
	at [email protected]//org.hibernate.proxy.AbstractLazyInitializer.errorIfReadOnlySettingNotAvailable(AbstractLazyInitializer.java:343)
	at [email protected]//org.hibernate.proxy.AbstractLazyInitializer.isReadOnly(AbstractLazyInitializer.java:356)
	... 227 more

It seems that is trying to serialize the hibernate implementation of the LazyInitializer (ByteBuddy). I tried to use a Serializer to force Json-B to serialize the real object.

public class HibernateProxySerializer implements JsonbSerializer<HibernateProxy> {

    @Override
    public void serialize(HibernateProxy object, JsonGenerator generator, SerializationContext serializer) {
            serializer.serialize(object.getHibernateLazyInitializer().getImplementation(), generator);
        }
}

The serializer seems to be used only when the root object is a proxy. If i pass any object that is not a proxy but contains one, the serializer is not used and the exception above is thrown. I suspect this is because the class of an HibernateProxy has inner classes and it is not the main class like <some_class>$HibernateProxy$<some_reference> hence:

private void marshallProperty(T object, JsonGenerator generator, SerializationContext ctx, PropertyModel propertyModel) {
        (...)
            final JsonbSerializer<?> propertyCachedSerializer = propertyModel.getPropertySerializer(); 

Fails to find a UserSerializer for this.

The first workaround i found was to force the getter of a property (GetFromGetter) to unproxy the object. This is not optimal for the code and working around it involves using an oddly named function for the business logic "get" and the regular one for Json-B only . For code cleanup, it would be nice if we could annotate a property/method with @JsonbGetter to define the getter Json-B should use.

The second workaround was to create a visibility strategy that blocked fields or functions of type LazyInitializer.class and ProxyConfiguration.class . This is also not optimal but it seems to get the work done. I noticed that Yasson's default visibility strategy has the ability to look both at the field and method to judge the visibility of each, which is not possible in a custom visibility strategy, specially to enforce the spec.

I would like to have the propertyModel.getPropertySerializer() to return my JsonbSerializer<HibernateProxy> for property serialization, not just the root object !

Thanks


Wildfly 18.0.1 Final Yasson 1.0.5 JDK 11 MySQL Connector 8.0.15

pedroosorio avatar Jan 22 '20 15:01 pedroosorio

hi @pedroosorio thanks for reporting this issue. Can you please include an example of the Java model class you are using in this case, along with indicating which fields are hibernate proxy objects?

aguibert avatar Jan 22 '20 16:01 aguibert

Hey @aguibert !

@Entity
@Table(name = "Account")
public class Account implements Serializable {
    (...)
    @JoinColumn(name = "pricePlanCurrent", referencedColumnName = "id")
    @ManyToOne(fetch = FetchType.LAZY)
    private PricePlan pricePlanCurrent;
    (...)
}

@Entity
@Cacheable
@Table(name = "PricePlan")
public class PricePlan implements Serializable {
    (...)
    @JoinColumn(name = "tenant", referencedColumnName = "id")
    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    private Tenant tenant;
    (...)
}

When calling Jsonb.toJson(account.getPricePlanCurrent()) the root object is of class PricePlan$HibernateProxy$something and the serializer is engaged, because this goes through serializeRoot(). The object tenant inside of the pricePlanCurrent is not serialized using my serializer, it goes through ObjectSerializer's marshallProperty(). The same goes if i do Jsonb.toJson(account). It is crucial to have this because a proxy might be out of session an uninitialized and i need to be able handle that properly in my serializer !

pedroosorio avatar Jan 22 '20 18:01 pedroosorio

@aguibert I forgot to state that the properties with FetchType.LAZY are the ones that become proxies.

I also found another issue. When serializing a PricePlan object that is a proxy (hence the class is PricePlan$HibernateProxy$Something) the annotations present in the class methods, @JsonbTransient for instance, are not carried to the proxy methods. This makes the serializer output properties (like the tenant) when it is annotated with @JsonbTransient on the method declaration.

public class PricePlan implements Serializable {
    @JoinColumn(name = "tenant", referencedColumnName = "id")
    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    private Tenant tenant;

   @JsonbTransient
   public Tenant getTenant() {
       return this.tenant;
   }
}

Yasson is also creating a PropertyModel for PricePlan and another for PricePlan$HibernateProxy$something so they behave differently. I came up with the following workaround on the visibility strategy to check for the @JsonbTransient annotation:

private boolean isJsonbTransient(Method method) {
        if (method.isAnnotationPresent(JsonbTransient.class)) {
            return true;
        }

        Class<?> declaringClass = method.getDeclaringClass();
        try {
            if (HibernateProxy.class.isAssignableFrom(declaringClass)) {
                declaringClass = declaringClass.getSuperclass();
            }

            Method original = declaringClass.getMethod(method.getName(), method.getParameterTypes());
            return original.isAnnotationPresent(JsonbTransient.class);
        } catch (NoSuchMethodException | SecurityException e) {
            return false;
        }
    }

On the other hand, if we annotate the property itself, it works !

pedroosorio avatar Jan 27 '20 17:01 pedroosorio

Maybe it is related to https://github.com/eclipse-ee4j/yasson/issues/303

nimo23 avatar Jan 30 '20 18:01 nimo23

@nimo23 well, yes and no. If i have the serializer works for all objects that are proxies, which is not the case, i can handle the uninitialized case there easily, i don't yasson to do that for me. @aguibert any news on this ?

pedroosorio avatar Feb 04 '20 08:02 pedroosorio

hi @pedroosorio, since Hibernate is commonly used with JSON-B/Yasson I'd like to come up with some sort of generic integration in Yasson. I like what you have proposed in your previous comment with the enhanced isJsonbTransient(Method method) code example that will defer to the superclass if the declaring class is an instance of HibernateProxy. Perhaps we need to add a special case for Hibernate like we've already done for Groovy and Weld (CDI) here: https://github.com/eclipse-ee4j/yasson/blob/b03dc0f42ef92c5c7f3206d711e2ad8935caf9f8/src/main/java/org/eclipse/yasson/internal/ClassParser.java#L208

Could you please upload a full set of reproducer code so I can further inspect this case and come up with a solution?

aguibert avatar Apr 06 '20 02:04 aguibert

Hey @aguibert will try to get that done in a timely manner !

Thank you !

pedroosorio avatar Apr 09 '20 09:04 pedroosorio

Hi As wildfly 25 upgraded their librairies with this version, this is a regression. Is there any fix for this issue, please?

dabbabi avatar Nov 29 '21 10:11 dabbabi