Saturday, May 29, 2010

Logically deleted entities in Hibernate Search

A recent project relies on Hibernate Search with a quite complex entity structure. We perform search on an entity that has several associated entities at multiple levels, and the search matches several text fields of the associated entities. Everything worked quite fine, although with 3.1.1.GA we had to workaround this bug: http://opensource.atlassian.com/projects/hibernate/browse/HSEARCH-391 (already fixed in 3.1.2).

The problem came when we started logically deleting associated entities (using a "deleted" boolean field), because the search still matched for text values of the deleted entities, and there seemed to be no proper way to exlude these indexes (and I think there is really not, as the indexes in lucene are 'flat'). Fortunately the hibernate community helped me out: https://forum.hibernate.org/viewtopic.php?f=9&t=1003745

In my parent entities I filtered out these deleted entites from the collections, but this didn't make Hibernate Search drop the related indexes. The solution suggested in the forum was to actually remove the deleted entities from the owner collection, so actually breaking the relation between the entities. This might not be a solution in every case - you might need to keep the relation - but it helped me fortunately.

Thursday, May 20, 2010

Using Vaadin with Seam

I'm a big fan of both frameworks. Unfortunately, the direct ajax model doesn't yet integrate as well as JSF does with Seam. I created a model which does the trick, but I'm not that content with it yet (see DAAM), and the implementation is still experimental. For my current project I have to create some simple administration interfaces, for which Vaadin is a really neat choice. And of course I don't want to give up the convenience of Seam.

There's a simple way of enabling Seam stuff in a Vaadin application, as also proposed here: http://vaadin.com/forum/-/message_boards/message/116273. This enables using Seam Contexts and Seam transaction management in your Vaadin application code. Somehow the solution didn't work out for me, so I created my own servlet filter which does the same two thing (contexts and transactions):

 @Override
 public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
  new ContextualHttpServletRequest((HttpServletRequest) request) {
   @Override
   public void process() throws Exception {
    try {
     beginTransaction();
     chain.doFilter(request, response);
    } finally {
     commitOrRollBack();
    }
   }
  }.run();
 }

The transaction handling methods are copy-pasted from the Seam JSF integration implementation:

/**
  * Code from SeamPhaseListener (2.2.0 GA)
  */
 public static void beginTransaction() {
  try {
   if (!Transaction.instance().isActiveOrMarkedRollback()) {
    Transaction.instance().begin();
   }
  } catch (Exception e) {
   throw new IllegalStateException("Could not start transaction", e);
  }
 }
 
 /**
  * Code from SeamPhaseListener (2.2.0 GA)
  */
 public static void commitOrRollBack() {
  try {
   if (Transaction.instance().isActive()) {
    try {
     Transaction.instance().commit();

    } catch (IllegalStateException e) {
     log.info("TX commit failed with illegal state exception. This may be " + "because the tx timed out and was rolled back in the background.", e);
    }
   } else if (Transaction.instance().isRolledBackOrMarkedRollback()) {
    Transaction.instance().rollback();
   }
  } catch (Exception e) {
   throw new IllegalStateException("Could not commit transaction", e);
  }
 }

But this is not all the way we can go. I want to use my EntityManager and other Seam components in my UI code. My UI classes are of course not Seam components (this is what is basically different in DAAM), but we can do a little trick. The methods in our UI classes are usually invoked by user interface events, eg. button clicks. I created a basic event delegate that, before actually invoking the delegated method, looks at the target object and handles its @In annotations. The implementation is quite simple:

public class InjectingEventDelegate {
 
 Object component;
 
 String methodName;
 
 public InjectingEventDelegate(Object component, String methodName) {
  this.component = component;
  this.methodName = methodName;
 }
 
 public void doDelegate() {
  inject();
  try {
   Method method = component.getClass().getMethod(methodName);
   method.invoke(component);
  } catch (Exception e) {
   throw new RuntimeException(e);
  }
 }
 
 protected void inject() {
  for (Field field : component.getClass().getDeclaredFields()) {
   if (field.isAnnotationPresent(In.class)) {
    In in = field.getAnnotation(In.class);
    String name = field.getName();
    if (!StringUtils.isEmpty(in.value()))
     name = in.value();
    Object toInject = Component.getInstance(name);
    if (toInject == null)
     throw new RuntimeException("Seam component with name '" + name + "' not found, trying to inject field " + field.getName() + " on " + component + " for invoking " + methodName);
    try {
     field.set(component, toInject);
    } catch (Exception e) {
     throw new RuntimeException("Count not inject field " + field.getName() + " on " + component + " for invoking " + methodName + ". Is the field declared public?", e);
    }
   }
  }
 }

}

To help binding these delegates, I created an annotation based action binder, as follows

public class ActionBinder {
 
 public static void bind(Object component) {
  for (Field field : component.getClass().getDeclaredFields()) {
   if (field.isAnnotationPresent(ActionBinding.class)) {
    ActionBinding actionBinding = field.getAnnotation(ActionBinding.class);
    try {
     Object fieldValue = field.get(component);
     Object delegate = new InjectingEventDelegate(component, actionBinding.value());
     if (fieldValue instanceof Button) {
      ((Button)fieldValue).addListener(ClickEvent.class, delegate, "doDelegate");
     }
    } catch (Exception e) {
     throw new RuntimeException(e);
    }
   }
  }
 }

}

And that's it. We can now create UIs like this:

public class Page extends VerticalLayout {
 
 @ActionBinding("add")
 public Button addButton;

        @In
        public EntityManager em;

        public Page() {
            addButton = new Button("add");
            addComponent(add);
            ActionBinder.bind(this);
        }

        public void add() {
            // do operations with EntityManager injected.
        }
}

Wednesday, May 19, 2010

Recently I had to port a solution consisting of several Seam applications from JBoss AS 4.2.2 to Glassfish v2.1. This had some tricky parts, mainly because Glassfish is strict about J2EE standard, while JBoss AS is quite not.

The exception which needed the most time to figure out was the one below:

WARNING: JTS5054: Unexpected error occurred in after completion
java.lang.IllegalStateException: No event context active
 at org.jboss.seam.ScopeType.getContext(ScopeType.java:121)
 at org.jboss.seam.Component.getInstance(Component.java:1999)
 at org.jboss.seam.Component.getInstance(Component.java:1994)
 at org.jboss.seam.Component.getInstance(Component.java:1967)
 at org.jboss.seam.Component.getInstance(Component.java:1962)
 at org.jboss.seam.transaction.Transaction.instance(Transaction.java:39)
 at org.jboss.seam.persistence.ManagedPersistenceContext.close(ManagedPersistenceContext.java:205)
 at org.jboss.seam.persistence.ManagedPersistenceContext.afterCompletion(ManagedPersistenceContext.java:194)
 at com.sun.jts.jta.SynchronizationImpl.after_completion(SynchronizationImpl.java:154)
 at com.sun.jts.CosTransactions.RegisteredSyncs.distributeAfter(RegisteredSyncs.java:210)
 at com.sun.jts.CosTransactions.TopCoordinator.afterCompletion(TopCoordinator.java:2585)
 at com.sun.jts.CosTransactions.CoordinatorTerm.commit(CoordinatorTerm.java:433)
 at com.sun.jts.CosTransactions.TerminatorImpl.commit(TerminatorImpl.java:250)
 at com.sun.jts.CosTransactions.CurrentImpl.commit(CurrentImpl.java:623)
 at com.sun.jts.jta.TransactionManagerImpl.commit(TransactionManagerImpl.java:309)
 at com.sun.enterprise.distributedtx.J2EETransactionManagerImpl.commit(J2EETransactionManagerImpl.java:1029)
 at com.sun.enterprise.distributedtx.J2EETransactionManagerOpt.commit(J2EETransactionManagerOpt.java:398)
 at com.sun.enterprise.distributedtx.UserTransactionImpl.commit(UserTransactionImpl.java:197)
 at org.jboss.seam.transaction.UTTransaction.commit(UTTransaction.java:52)
 at *.SeamIntegrationFilter.commitOrRollBack(SeamIntegrationFilter.java:64)
 at *.SeamIntegrationFilter$1.process(SeamIntegrationFilter.java:38)
 at org.jboss.seam.servlet.ContextualHttpServletRequest.run(ContextualHttpServletRequest.java:53)
 at *.SeamIntegrationFilter.doFilter(SeamIntegrationFilter.java:41)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:246)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:214)
 at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:313)
 at org.apache.catalina.core.StandardContextValve.invokeInternal(StandardContextValve.java:287)
 at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:218)
 at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:648)
 at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:593)
 at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:94)
 at com.sun.enterprise.web.PESessionLockingStandardPipeline.invoke(PESessionLockingStandardPipeline.java:98)
 at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:222)
 at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:648)
 at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:593)
 at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:587)
 at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:1096)
 at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:166)
 at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:648)
 at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:593)
 at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:587)
 at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:1096)
 at org.apache.coyote.tomcat5.CoyoteAdapter.service(CoyoteAdapter.java:288)
 at com.sun.enterprise.web.connector.grizzly.DefaultProcessorTask.invokeAdapter(DefaultProcessorTask.java:647)
 at com.sun.enterprise.web.connector.grizzly.DefaultProcessorTask.doProcess(DefaultProcessorTask.java:579)
 at com.sun.enterprise.web.connector.grizzly.DefaultProcessorTask.process(DefaultProcessorTask.java:831)
 at com.sun.enterprise.web.connector.grizzly.DefaultReadTask.executeProcessorTask(DefaultReadTask.java:341)
 at com.sun.enterprise.web.connector.grizzly.DefaultReadTask.doTask(DefaultReadTask.java:263)
 at com.sun.enterprise.web.connector.grizzly.DefaultReadTask.doTask(DefaultReadTask.java:214)
 at com.sun.enterprise.web.portunif.PortUnificationPipeline$PUTask.doTask(PortUnificationPipeline.java:380)
 at com.sun.enterprise.web.connector.grizzly.TaskBase.run(TaskBase.java:265)
 at com.sun.enterprise.web.connector.grizzly.ssl.SSLWorkerThread.run(SSLWorkerThread.java:106)

The above stack trace is of calling a remote EJB from a Seam integrated Vaadin application. However, a similar exception was thrown if I called the remote EJB method from a single (command line) client. The EJB method is marked as transactional (REQUIRED).

The issue seemed to be related to this: https://jira.jboss.org/browse/JBSEAM-3778 , althought I have no MDBs in the scenario. Upgrading to 2.2.1CR1 solved the problem.