Showing posts with label seam. Show all posts
Showing posts with label seam. Show all posts

Monday, October 25, 2010

Seam, MDB, EJB and glassfish coming together

The situation seems to be quite complex but I think it can happen to anyone :) So I have a Glassfish 2.1 appserver with Seam 2.2GA. I use several Seam components in the web tier and they interact with stateless EJBs as well, because some functionality has to be accessible through a remote EJB interface.

Injecting EJBs to Seam components are quite simple, just use the @In annotation. Seam will notice that it's a session bean, and use the default jndi name to look it up, as configured in the components.xml:

    <core:init jndi-pattern="java:comp/env/YOUR-APP-NAME/#{ejbName}/local"/>

You can also inject Seam components to session beans, using the same @In annotation. Everything works fine, however, you have to define your local EJB references in the web.xml (on Glassfish), so that Seam can look them up from the web tier as well. Otherwise, local interfaces are not accessible from the web tier. The case is similar for Message Driven Beans (MDBs). Local interface references have to be declared.

If you just use @In annotations in your MDB (or any seam component invoked from the MDB), you'll get NameNotFoundExceptions for "java:comp/env/YOUR-APP-NAME/YOUR-EJB-NAME/local".

You have to declare your ejb reference in your MDB, and the easiest way to do it is to use @EJB annotations in your MDB class. This would be fine, but by default, the JNDI name will be "java:comp/env/your.message.driven.bean.full.ClassName/referenceVariableName", so you have to override it by using the @EJB(name="YOUR-APP-NAME/YOUR-EJB-NAME/local" annotation. By declaring this, Glassfish will make your local EJB interfaces accessible for the code running from the MDB.

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.