Can you spot the problem?

2014-03-28  |   |  Java  

I am working on Hibernate Search's ability to provide field bridge autodiscovery. I am usually pretty OK at getting a green bar on first run but I got out of luck today.

Can you spot the problem?

 org.hibernate.HibernateException: Error while indexing in Hibernate Search (before transaction completion)
     at org.hibernate.search.backend.impl.EventSourceTransactionContext$DelegateToSynchronizationOnBeforeTx.doBeforeTransactionCompletion(EventSourceTransactionContext.java:194)
     at org.hibernate.engine.spi.ActionQueue$BeforeTransactionCompletionProcessQueue.beforeTransactionCompletion(ActionQueue.java:707)
     at org.hibernate.engine.spi.ActionQueue.beforeTransactionCompletion(ActionQueue.java:387)
     at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:516)
     at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:105)
     at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:177)
     at org.hibernate.search.test.bridge.ArrayBridgeTest.prepareData(ArrayBridgeTest.java:95)
     at org.hibernate.search.test.bridge.ArrayBridgeTest.setUp(ArrayBridgeTest.java:58)
     at org.hibernate.search.test.SearchTestCase.runBare(SearchTestCase.java:191)
     at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:84)
     at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
     at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:67)
 Caused by: org.hibernate.search.bridge.BridgeException: Exception while calling bridge#set
     class: org.hibernate.search.test.bridge.ArrayBridgeTestEntity
     path: dates
     at org.hibernate.search.bridge.util.impl.ContextualExceptionBridgeHelper.buildBridgeException(ContextualExceptionBridgeHelper.java:101)
     at org.hibernate.search.bridge.util.impl.ContextualExceptionBridgeHelper$OneWayConversionContextImpl.set(ContextualExceptionBridgeHelper.java:130)
     at org.hibernate.search.engine.spi.DocumentBuilderIndexedEntity.buildDocumentFields(DocumentBuilderIndexedEntity.java:449)
     at org.hibernate.search.engine.spi.DocumentBuilderIndexedEntity.getDocument(DocumentBuilderIndexedEntity.java:376)
     at org.hibernate.search.engine.spi.DocumentBuilderIndexedEntity.createAddWork(DocumentBuilderIndexedEntity.java:292)
     at org.hibernate.search.engine.spi.DocumentBuilderIndexedEntity.addWorkToQueue(DocumentBuilderIndexedEntity.java:235)
     at org.hibernate.search.engine.impl.WorkPlan$PerEntityWork.enqueueLuceneWork(WorkPlan.java:506)
     at org.hibernate.search.engine.impl.WorkPlan$PerClassWork.enqueueLuceneWork(WorkPlan.java:279)
     at org.hibernate.search.engine.impl.WorkPlan.getPlannedLuceneWork(WorkPlan.java:165)
     at org.hibernate.search.backend.impl.WorkQueue.prepareWorkPlan(WorkQueue.java:131)
     at org.hibernate.search.backend.impl.BatchedQueueingProcessor.prepareWorks(BatchedQueueingProcessor.java:73)
     at org.hibernate.search.backend.impl.PostTransactionWorkQueueSynchronization.beforeCompletion(PostTransactionWorkQueueSynchronization.java:87)
     at org.hibernate.search.backend.impl.EventSourceTransactionContext$DelegateToSynchronizationOnBeforeTx.doBeforeTransactionCompletion(EventSourceTransactionContext.java:191)
     ... 19 more
 Caused by: java.lang.ClassCastException: [Ljava.util.Date; cannot be cast to java.util.Date
     at org.hibernate.search.bridge.builtin.DateBridge.objectToString(DateBridge.java:90)
     at org.hibernate.search.bridge.builtin.impl.String2FieldBridgeAdaptor.set(String2FieldBridgeAdaptor.java:46)
     at org.hibernate.search.bridge.util.impl.ContextualExceptionBridgeHelper$OneWayConversionContextImpl.set(ContextualExceptionBridgeHelper.java:127)
     ... 30 more

Let me zoom a bit for you.

Caused by: java.lang.ClassCastException: [Ljava.util.Date; cannot be cast to java.util.Date

What? java.util.Date cannot be cast to java.util.Date???? That is usually a good sign that you're mixing classloaders and that the two objects comes from different ones. Except that in my unit test, I don't mess around with classloaders.

That's when it hit me. Do you see the [L? It means that the first type is not java.util.Date but java.util.Date[]. Now the ClassClastException makes perfect sense.

Conclusion?

I'm a idiot or (preferred one) this error message needs a serious UX take over. The code is dying already. Why not take the few extra nanoseconds to represent the types in a readable way?

Caused by: java.lang.ClassCastException: java.util.Date[] cannot be cast to java.util.Date

I lost 5 minutes instead of 5 seconds. When you write your next error report or exception, think of the children! Or rather the poor souls that will have to use your library or code.

You can find more info on the [L syntax on stackoverflow.


Thou shalt be binary compatible

2012-11-23  |   |  Java  

I learned some new tricks today thanks to Gunnar around backward compatibilities in Java.

There is compatibility and compatibility

In Bean Validation, we need to fix a mistake I made. One easy solution is to create a sub-interface and return that sub-interface.

//API
public interface Contract {
    public Result testMe();
    public Result testMeMore();

    public static interface Result {
        void doIt();
    }
}

//client code
Contract contract = new ContractImpl();
contract.testMeMore().doIt();

In our example, I need to add a new method to results coming from testMeMore(). I thought of this approach:

//API
public interface Contract {
    public Result testMe();
    public ResultMore testMeMore();

    public static interface Result {
        void doIt();
    }

    public static interface ResultMore extends Result {
        void doItAgain();
    }
}

//client code
Contract contract = new ContractImpl();
contract.testMeMore().doIt();

This approach is source compatible: if I recompile the client code, things will work perfectly. But it's not backward compatible at the binary level.

If you don't recompile the client code and simply update the API jar, you will get a nasty exception

Exception in thread "main" java.lang.NoSuchMethodError: com.jboss.test.Contract.testMeMore()Lcom/jboss/test/Contract$Result;

That's because the contract is now com.jboss.test.Contract.testMeMore()Lcom/jboss/test/Contract$ResultMore and even if Result is a super interface, Java does not let go with it.

And since application deployed in Java EE 6 are supposed to work out of the box for Java EE 7, we can't do that. Note that testing binary compatibilities is not trivial.

Use the erasure hack, Luke

The what? It turns out the Java designers already had this problem when they introduced the generics type system. You can solve the problem by using intersection types.

//API
public interface Contract {
    public Result testMe();
    public <T extends Result & ResultMore> T testMeMore();

    public static interface Result {
        void doIt();
    }

    public static interface ResultMore extends Result {
        void doItAgain();
    }
}

//client code
Contract contract = new ContractImpl();
contract.testMeMore().doIt();

Because generic types are erased by their most left upper bounds which is Result in our case, things work out smoothly.

By the way, you can reproduce this ad nauseam.

//API
public interface Contract {
    public Result testMe();
    public <T extends Result & ResultMore & ResultUltimate> T testMeMore();

    public static interface Result {
        void doIt();
    }

    public static interface ResultMore extends Result {
        void doItAgain();
    }

    public static interface ResultUltimate extends Result {
        void doItForEver();
    }

}

//client code
Contract contract = new ContractImpl();
contract.testMeMore().doItAgain();

Conclusion

Now the big question is, should we use this trick in Bean Validation to solve this problem. What's your take on it?


No more Java Preferences for you!

2012-10-31  |   |  Java   Mac OS X  

Java on Max OS X is a moving target to say the least since stewardship has moved from Apple to Oracle. I had a lot of trouble to make Eclipse run on my machine making me feel like a customer of The Soup Nazi in Seinfeld.

Let me explain some changes.

The failure

I tried to run Eclipse Juno on my machine and got the following encouraging error

The JVM shared library /Library/Java/JavaVirtualMachines/openjdk-1.7-x86_64 does not contain the JNI_CreateJavaVM symbol

Eclipse always uses the system default JVM on Mac OS X and as far as I know you can't change that. In theory that's not a big deal, you just have to change the default JVM to use via the Java Preferences application.

No more Java Preferences application

Apple recently removed the Java Preferences application from Mac OS X as they deemed it to be useless. In a sort of twisted way - aka we don't care about developers - they were right.

It has been replaced in the Java Oracle distribution by a panel in System Preferences under the Other category. Well except that this panel only deals with Oracle JVMs. So if you happen to have OpenJDK or any other JVM installed, you can not choose (or "unchoose") them.

The side effect for me was that OpenJDK was selected as the default JVM and this created this user friendly error when starting Eclipse.

Hats off to Henri Gomez for helping me find a way out. You basically need to go to /Library/Java/JavaVirtualMachines and remove OpenJDK

sudo rm -fR openjdk-1.7-x86_64

In my case I actually moved it somewhere else. What if I want to use OpenJDK too? F**k you! told me Orapple.

The combination of:

  • Apple stopping bundling the JVM
  • Oracle coming up with an Oracle only replacement
  • Apple removing a useful tool

makes the open JDK community at large feel quite unwelcome. So much for an open source project and community where both Apple and Oracle are major stakeholders.

By the way that's not the only glitch I've experienced when moving from the Apple VM to Oracle VM. JAVA_HOME now points to the JDK instead of the JRE.

Anyways, moving along.


Name: Emmanuel Bernard
Bio tags: French, Open Source actor, Hibernate, (No)SQL, JCP, JBoss, Snowboard, Economy
Employer: JBoss by Red Hat
Resume: LinkedIn
Team blog: in.relation.to
Personal blog: No relation to
Microblog: Twitter, Google+
Geoloc: Paris, France

Tags