Why Teneo wont work with Ehcache?

Posted by {"name"=>"Palash Ray", "email"=>"paawak@gmail.com", "url"=>"https://www.linkedin.com/in/palash-ray/"} on April 03, 2009 · 5 mins read

The first problem is that by default EMF classes are not Serializable. The very first stack trace that you will get in two parts will look something like:

Stack Trace I

java.lang.ClassCastException: xxx.impl.SomeEmfClassImpl
at org.hibernate.type.AbstractType.disassemble(AbstractType.java:78)
at org.hibernate.type.TypeFactory.disassemble(TypeFactory.java:474)
at org.hibernate.cache.entry.CacheEntry.(CacheEntry.java:69)
at org.hibernate.engine.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:164)
at org.hibernate.loader.Loader.initializeEntitiesAndCollections(Loader.java:877)
at org.hibernate.loader.Loader.doQuery(Loader.java:752)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:259)

Stack Trace II

**Serious** 04/04/09 12:08:15 PM    org.hibernate.HibernateException: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: Properties.trail
at org.hibernate.engine.Collections.processDereferencedCollection(Collections.java:119)
at org.hibernate.engine.Collections.processUnreachableCollection(Collections.java:62)
at org.hibernate.event.def.AbstractFlushingEventListener.flushCollections(AbstractFlushingEventListener.java:241)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:100)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:49)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1027)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:365)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:137)

Stack Trace II is most confusing. If you trust Stack Trace II, it will lead you off on a false scent. You will think that there is a problem with the way EMF handles collections. You will constatntly rant and spit fire at them, thinking there is nothing to be done except selectively disabling caching for those queries.
But friends, here is the killer: Stack Trace II is caused by Stack Trace I. So let us explore that first. If you look at the very first line: ClassCastException. What has caused this? Let us see.
at org.hibernate.type.AbstractType.disassemble(AbstractType.java:78)
If you look at the source code AbstractType.disassemble() looks like:

	public Serializable disassemble(Object value, SessionImplementor session, Object owner)
	throws HibernateException {
		if (value==null) {
			return null;
		}
		else {
			return (Serializable) deepCopy( value, session.getEntityMode(), session.getFactory() );
		}
	}

So in line 78, the EMF model is casted into Serializable, and hence the cause of exception.

Solution

There is no other go, but to make the EMF models Serializable. How do we do that? We have to edit the default JET Templates so that all generated interfaces by EMF are Serializable. Here is an excellent article which will guide you. The template to edit is templates/model/Class.javajet.

  <%if (isImplementation) {%>
  public<%if (genClass.isAbstract()) {%> abstract<%}%> class <%=genClass.getClassName()%><%=genClass.getTypeParameters().trim()%><%=genClass.getClassExtends()%><%=genClass.getClassImplements()%>
  <%} else {%>
  public interface <%=genClass.getInterfaceName()%><%=genClass.getTypeParameters().trim()%><%=genClass.getInterfaceExtends()%>
  <%}%>, java.io.Serializable
  {

Now re-generate the model code from genmodel and try again.
With this, some operations, of course will work. But if you are using Teneo lesser than 1.0.4 (1.0.3 is the latest release as of now ), for most of the operations you will get a trace which looks something like:

Stack Trace III

04/04/09 01:05:47.164 PM  131221 WARN  org.apache.struts.chain.commands.AbstractExceptionHandler - Unhandled exception
java.lang.NullPointerException
at xxx.MyInterceptor.getEntityName(MyInterceptor.java:41)
at org.hibernate.impl.SessionImpl.guessEntityName(SessionImpl.java:1792)
at org.hibernate.impl.SessionImpl.bestGuessEntityName(SessionImpl.java:1759)
at org.eclipse.emf.teneo.hibernate.mapping.econtainer.EContainerUserType.assemble(EContainerUserType.java:300)
at org.hibernate.type.TypeFactory.assemble(TypeFactory.java:443)
at org.hibernate.cache.entry.CacheEntry.assemble(CacheEntry.java:119)

Before you get lost in the trace, lets take a quick look at what actually happens. Hibernate encounters a cache-entry and tries to make sense of it and construct the actual object queried. In the process, it is delegated to the TypeFactory.assemble(), which determines the type of the entry and delegates it to an implementation of org.hibernate.type.Type. So far so good. So what goes wrong?
at org.eclipse.emf.teneo.hibernate.mapping.econtainer.EContainerUserType.assemble(EContainerUserType.java:300)
If you have a look at the source code and then its super class, org.hibernate.type.AbstractType, at the assemble() method, you will notice that one significant difference is, in AbstractType.assemble(), if the cached parameter is null, it returns a null, assuming that Hibernate will fetch it from the Database instead.

	public Object assemble(Serializable cached, SessionImplementor session, Object owner)
	throws HibernateException {
		if ( cached==null ) {
			return null;
		}
		else {
			return deepCopy( cached, session.getEntityMode(), session.getFactory() );
		}
	}

While in EContainerUserType.assemble(), there is no null check that is what causes the big trace. Grab the Teneo sources, apply the fix, re-compile and bingo!

	public Object assemble(Serializable cached, SessionImplementor session, Object owner) throws HibernateException {
	    // palash: fix for ALL our teneo/ehcache woes!!
        // if cache is null, just return null, without guessing; hibernate is smart enough to fetch it from the db
        if (cached == null) {
            return null;
        }
		// already correct
		if (!(cached instanceof ContainerPointer)) {
			final String entityName = session.bestGuessEntityName(cached);
			final Serializable idObject = getID(entityName, cached, session);
			return session.internalLoad(entityName, idObject, false, false);
		} else {
			final ContainerPointer cp = (ContainerPointer) cached;
			return cp.getObject(session);
		}
	}

This fix will be available in Teneo 1.0.4 release. I have raised a bug on this and Martin has been kind enough to check it in.
For this article, I am using EMF 2.3.0_v_200706262000, Teneo 0.8_v_200708101732,  Hibernate 3.3.1.GA and Ehcache 1.5.0. You will find the Teneo sources in the public CVS repository here
:pserver:anonymous@dev.eclipse.org:/cvsroot/modeling.