yasson
yasson copied to clipboard
Error serializing HibernateProxy properties (member variables)
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
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?
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 !
@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 !
Maybe it is related to https://github.com/eclipse-ee4j/yasson/issues/303
@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 ?
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?
Hey @aguibert will try to get that done in a timely manner !
Thank you !
Hi As wildfly 25 upgraded their librairies with this version, this is a regression. Is there any fix for this issue, please?