1
votes

I experience what I currently analyse as a bug with hibernate 3.6.2-final and ehcache 2.6.2.

I recently started to use a custom type to handle values stored as "OUI"/"NON" in database as booleans in java.

I am using an adaptation of org.hibernate.type.YesNoType . This part works.

When I turn ehcache on, I get the following bug on a cache hit : an attempt to set a string value is performed on the object being processed, which fails because the property is a boolean...

In my log, I have : IllegalArgumentException in class: fr.senat.base_senateurs.libsen.hbm.Dpt, setter method of property: evetempub expected type: java.lang.Boolean, actual value: java.lang.String

The two interesting exceptions are :

org.hibernate.PropertyAccessException: IllegalArgumentException occurred while calling setter of fr.senat.base_senateurs.libsen.hbm.Dpt.evetempub
org.hibernate.property.BasicPropertyAccessor$BasicSetter.set(BasicPropertyAccessor.java:128)
org.hibernate.tuple.entity.AbstractEntityTuplizer.setPropertyValues(AbstractEntityTuplizer.java:583)
org.hibernate.tuple.entity.PojoEntityTuplizer.setPropertyValues(PojoEntityTuplizer.java:229)
org.hibernate.persister.entity.AbstractEntityPersister.setPropertyValues(AbstractEntityPersister.java:3847)
org.hibernate.cache.entry.CacheEntry.assemble(CacheEntry.java:137)
org.hibernate.cache.entry.CacheEntry.assemble(CacheEntry.java:104)
org.hibernate.event.def.DefaultLoadEventListener.assembleCacheEntry(DefaultLoadEventListener.java:651)
org.hibernate.event.def.DefaultLoadEventListener.loadFromSecondLevelCache(DefaultLoadEventListener.java:605)
org.hibernate.event.def.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:459)
org.hibernate.event.def.DefaultLoadEventListener.load(DefaultLoadEventListener.java:227)
org.hibernate.event.def.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:147)
org.hibernate.impl.SessionImpl.fireLoad(SessionImpl.java:1090)
org.hibernate.impl.SessionImpl.immediateLoad(SessionImpl.java:1026)
org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:176)
org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:215)
org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)
fr.senat.base_senateurs.libsen.hbm.Dpt_$$_javassist_77.getDptlib(Dpt_$$_javassist_77.java)
sun.reflect.GeneratedMethodAccessor106.invoke(Unknown Source)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:601)
javax.el.BeanELResolver.getValue(BeanELResolver.java:87)
javax.el.CompositeELResolver.getValue(CompositeELResolver.java:67)
org.apache.myfaces.el.unified.resolver.FacesCompositeELResolver.getValue(FacesCompositeELResolver.java:179)
org.apache.el.parser.AstValue.getValue(AstValue.java:183)
org.apache.el.ValueExpressionImpl.getValue(ValueExpressionImpl.java:185)
org.apache.webbeans.el.WrappedValueExpression.getValue(WrappedValueExpression.java:68)
org.apache.myfaces.view.facelets.el.ContextAwareTagValueExpression.getValue(ContextAwareTagValueExpression.java:96)
javax.faces.component._DeltaStateHelper.eval(_DeltaStateHelper.java:249)
javax.faces.component.UIOutput.getValue(UIOutput.java:67)
org.apache.myfaces.shared.renderkit.RendererUtils.getValue(RendererUtils.java:491)
org.apache.myfaces.shared.renderkit.RendererUtils.getStringValue(RendererUtils.java:334)
org.apache.myfaces.shared.renderkit.html.HtmlTextRendererBase.renderOutput(HtmlTextRendererBase.java:92)
org.apache.myfaces.shared.renderkit.html.HtmlTextRendererBase.encodeEnd(HtmlTextRendererBase.java:79)
javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:663)
javax.faces.component.UIComponentBase.encodeAll(UIComponentBase.java:543)
javax.faces.component.UIComponentBase.encodeAll(UIComponentBase.java:539)
org.primefaces.component.datatable.DataTableRenderer.encodeRegularCell(DataTableRenderer.java:743)
org.primefaces.component.datatable.DataTableRenderer.encodeRow(DataTableRenderer.java:695)
org.primefaces.component.datatable.DataTableRenderer.encodeTbody(DataTableRenderer.java:603)
org.primefaces.component.datatable.DataTableRenderer.encodeRegularTable(DataTableRenderer.java:220)
org.primefaces.component.datatable.DataTableRenderer.encodeMarkup(DataTableRenderer.java:192)
org.primefaces.component.datatable.DataTableRenderer.encodeEnd(DataTableRenderer.java:74)
javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:663)
javax.faces.component.UIData.encodeEnd(UIData.java:1699)
javax.faces.component.UIComponentBase.encodeAll(UIComponentBase.java:543)
javax.faces.component.UIComponentBase.encodeAll(UIComponentBase.java:539)
javax.faces.component.UIComponentBase.encodeAll(UIComponentBase.java:539)
javax.faces.component.UIComponentBase.encodeAll(UIComponentBase.java:539)
org.apache.myfaces.view.facelets.FaceletViewDeclarationLanguage.renderView(FaceletViewDeclarationLanguage.java:1981)
org.apache.myfaces.application.ViewHandlerImpl.renderView(ViewHandlerImpl.java:285)
javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:59)
javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:59)
org.apache.myfaces.lifecycle.RenderResponseExecutor.execute(RenderResponseExecutor.java:116)
org.apache.myfaces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:241)
org.apache.myfaces.extensions.cdi.jsf2.impl.listener.phase.CodiLifecycleWrapper.render(CodiLifecycleWrapper.java:126)
javax.faces.webapp.FacesServlet.service(FacesServlet.java:199)
fr.senat.faces.filters.HibernateNoSessionRequestFilter.doFilter(HibernateNoSessionRequestFilter.java:44)

and

java.lang.IllegalArgumentException: java.lang.ClassCastException@4194c10f
sun.reflect.GeneratedMethodAccessor129.invoke(Unknown Source)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:601)
org.hibernate.property.BasicPropertyAccessor$BasicSetter.set(BasicPropertyAccessor.java:66)
org.hibernate.tuple.entity.AbstractEntityTuplizer.setPropertyValues(AbstractEntityTuplizer.java:583)
org.hibernate.tuple.entity.PojoEntityTuplizer.setPropertyValues(PojoEntityTuplizer.java:229)
org.hibernate.persister.entity.AbstractEntityPersister.setPropertyValues(AbstractEntityPersister.java:3847)
org.hibernate.cache.entry.CacheEntry.assemble(CacheEntry.java:137)
org.hibernate.cache.entry.CacheEntry.assemble(CacheEntry.java:104)
org.hibernate.event.def.DefaultLoadEventListener.assembleCacheEntry(DefaultLoadEventListener.java:651)
org.hibernate.event.def.DefaultLoadEventListener.loadFromSecondLevelCache(DefaultLoadEventListener.java:605)
org.hibernate.event.def.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:459)
org.hibernate.event.def.DefaultLoadEventListener.load(DefaultLoadEventListener.java:227)
org.hibernate.event.def.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:147)
org.hibernate.impl.SessionImpl.fireLoad(SessionImpl.java:1090)
org.hibernate.impl.SessionImpl.immediateLoad(SessionImpl.java:1026)
org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:176)
org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:215)
org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)
fr.senat.base_senateurs.libsen.hbm.Dpt_$$_javassist_77.getDptlib(Dpt_$$_javassist_77.java)
sun.reflect.GeneratedMethodAccessor106.invoke(Unknown Source)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:601)
javax.el.BeanELResolver.getValue(BeanELResolver.java:87)
javax.el.CompositeELResolver.getValue(CompositeELResolver.java:67)
org.apache.myfaces.el.unified.resolver.FacesCompositeELResolver.getValue(FacesCompositeELResolver.java:179)
org.apache.el.parser.AstValue.getValue(AstValue.java:183)
org.apache.el.ValueExpressionImpl.getValue(ValueExpressionImpl.java:185)
org.apache.webbeans.el.WrappedValueExpression.getValue(WrappedValueExpression.java:68)
org.apache.myfaces.view.facelets.el.ContextAwareTagValueExpression.getValue(ContextAwareTagValueExpression.java:96)
javax.faces.component._DeltaStateHelper.eval(_DeltaStateHelper.java:249)
javax.faces.component.UIOutput.getValue(UIOutput.java:67)
org.apache.myfaces.shared.renderkit.RendererUtils.getValue(RendererUtils.java:491)
org.apache.myfaces.shared.renderkit.RendererUtils.getStringValue(RendererUtils.java:334)
org.apache.myfaces.shared.renderkit.html.HtmlTextRendererBase.renderOutput(HtmlTextRendererBase.java:92)
org.apache.myfaces.shared.renderkit.html.HtmlTextRendererBase.encodeEnd(HtmlTextRendererBase.java:79)
javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:663)
javax.faces.component.UIComponentBase.encodeAll(UIComponentBase.java:543)
javax.faces.component.UIComponentBase.encodeAll(UIComponentBase.java:539)
org.primefaces.component.datatable.DataTableRenderer.encodeRegularCell(DataTableRenderer.java:743)
org.primefaces.component.datatable.DataTableRenderer.encodeRow(DataTableRenderer.java:695)
org.primefaces.component.datatable.DataTableRenderer.encodeTbody(DataTableRenderer.java:603)
org.primefaces.component.datatable.DataTableRenderer.encodeRegularTable(DataTableRenderer.java:220)
org.primefaces.component.datatable.DataTableRenderer.encodeMarkup(DataTableRenderer.java:192)
org.primefaces.component.datatable.DataTableRenderer.encodeEnd(DataTableRenderer.java:74)
javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:663)
javax.faces.component.UIData.encodeEnd(UIData.java:1699)
javax.faces.component.UIComponentBase.encodeAll(UIComponentBase.java:543)
javax.faces.component.UIComponentBase.encodeAll(UIComponentBase.java:539)
javax.faces.component.UIComponentBase.encodeAll(UIComponentBase.java:539)
javax.faces.component.UIComponentBase.encodeAll(UIComponentBase.java:539)
org.apache.myfaces.view.facelets.FaceletViewDeclarationLanguage.renderView(FaceletViewDeclarationLanguage.java:1981)
org.apache.myfaces.application.ViewHandlerImpl.renderView(ViewHandlerImpl.java:285)
javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:59)
javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:59)
org.apache.myfaces.lifecycle.RenderResponseExecutor.execute(RenderResponseExecutor.java:116)
org.apache.myfaces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:241)
org.apache.myfaces.extensions.cdi.jsf2.impl.listener.phase.CodiLifecycleWrapper.render(CodiLifecycleWrapper.java:126)
javax.faces.webapp.FacesServlet.service(FacesServlet.java:199)
fr.senat.faces.filters.HibernateNoSessionRequestFilter.doFilter(HibernateNoSessionRequestFilter.java:44)

Do you have an idea (except stop using a second level cache :-) ) ?

1

1 Answers

2
votes

Ok, so, in fact, the assumption that my custom type was good was not so true. I understood that it was something of a copy-paste of hibernate YesNo type, so I was confident... But it is not.

The problem was that its assemble and dissassemble methods were not coherent.

In disassemble, the value (a Boolean here) was transformed to a string by the following snippet :

@Override
public Serializable disassemble(Object value) {
    return (value == null) ? null : value.toString();
}

but in assemble, the String was left "as is" by the following snippet :

@Override
public Object assemble(Serializable cached, Object owner) {
    return (cached == null) ? null : cached;
}

a correct implementation is :

@Override
public Object assemble(Serializable cached, Object owner) {
    if(cached == null) {
        return null;
    }
    if(cached.equals("true")) {
        return true;
    }
    return false;
}

As the assemble/disassemble method pair was not used until some serialization was performed in cache, the bug was not detected until an application leveraging second level cache used it.

On a "regular" object load from database, AstractEntityPersister#hydrate is called by loadFromResultSet (line 1527 of org.hibernate.loader.Loader) hydrate performs custom type conversation by calling nullSafeGet on the type

On a cache hit, the cache entry is "split" by org.hibernate.cache.entry.StructuredCacheEntry#destructure, then assembled by assembleCacheEntry . At some point, it calls the custom type "assemble" method.

Note that for efficiency sake, I will try to avoid the string comparisons, but you get the idea... :-)