Monday, May 9, 2011

Testing a GWT/Mvp4g application in the JVM

I develop a client for a logistics system in GWT, using Mvp4g (currently GWT 2.3 with Mvp4g 1.3.1). The system has technically quite complex integration tests, where a J5EE based (Glassfish 2.1 + Seam 2.2) core application serves multiple WPF clients (with web services using WCF and Metro) and multiple GWT web clients. I use the same infrastructure to test the overall performance of the system. It might not have been the best decision, but I didn't want to use Selenium at that time, and HtmlUnit had (maybe still has) some issues with my application which I didn't want to sort out (although it should work), so I chose to instantiate my GWT application in the JVM, using mock views. The main application code is in the presenters anyway, so it should be easy to use from a JVM. Well, not that easy, but not a catastrophe.

Presenters and the EventBus

So, presenters should be instantiable without modification in the JVM. Any GWT UI related code should be in the views, that's not a big restriction. The EventBus itself is generated at compile time by Mvp4g, and I didn't want to use those generators, probably the generated code runs only in a browser, due to the use of Guice, but maybe I'm wrong :).

Anyway, the EventBus is quite simple to implement with reflection and dynamic proxies. What we have to do is basically:

  • contain an instance of each presenter, and bind them to their views,
  • maintain a list of presenters handling each event
  • and delegate each event method invocation to those presenters, using the method name convetion.
So here is the code I use:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import com.google.inject.Injector;
import com.mvp4g.client.annotation.Event;
import com.mvp4g.client.annotation.InitHistory;
import com.mvp4g.client.event.EventHandlerInterface;
import com.mvp4g.client.presenter.PresenterInterface;

public class EventBus<T extends com.mvp4g.client.event.EventBus> {
  
   Class<T> interfaceClass;
  
   T eventBus;
   @SuppressWarnings("unchecked")
   Map<Class<PresenterInterface>, PresenterInterface> presenters = new HashMap<Class<PresenterInterface>, PresenterInterface>();
   Map<String, EventDescriptor> events = new HashMap<String, EventDescriptor>();
   EventDescriptor initEvent;
  
   Injector injector;
  
   @SuppressWarnings("unchecked")
   public EventBus(Class<T> interfaceClass, Injector injector) {
       this.injector = injector;
       this.interfaceClass = interfaceClass;
       eventBus = (T) Proxy.newProxyInstance(EventBus.class.getClassLoader(),
               new Class [] { interfaceClass }, new EventBusInvocationHandler());
       for (Method method : interfaceClass.getMethods())
           if (method.isAnnotationPresent(Event.class)) {
               Event event = method.getAnnotation(Event.class);
               EventDescriptor eventDescriptor = new EventDescriptor();
               eventDescriptor.method = method;
               eventDescriptor.eventName = method.getName();
               eventDescriptor.targetMethodName = "on" + Character.toUpperCase(eventDescriptor.eventName.charAt(0)) + eventDescriptor.eventName.substring(1);
               for (Class<? extends EventHandlerInterface> cls : event.handlers())
                   eventDescriptor.handlers.add(getPresenter(cls));
               events.put(eventDescriptor.eventName, eventDescriptor);
               if (method.isAnnotationPresent(InitHistory.class))
                   initEvent = eventDescriptor;
           }
   }
  
   @SuppressWarnings("unchecked")
   public void bindView(Class<? extends PresenterInterface> presenterClass, Object view) {
       PresenterInterface presenter = getPresenter(presenterClass);
       presenter.setView(view);
       presenter.setEventBus(eventBus);
       presenter.bind();
   }
  
   public void init() {
       try {
           initEvent.method.invoke(eventBus);
       } catch (Exception e) {
           throw new RuntimeException(e);
       }
   }
  
   @SuppressWarnings("unchecked")
   public <T extends EventHandlerInterface> T getPresenter(Class<T> presenterClass) {
       T presenter = (T) presenters.get(presenterClass);
       if (presenter == null) {
           try {
               presenter = injector.getInstance(presenterClass);
               presenters.put((Class<PresenterInterface>) presenterClass, (PresenterInterface) presenter);
           } catch (Exception e) {
               throw new RuntimeException(e);
           }
       }
       return presenter;
   }
  
   public T getEventBus() {
       return eventBus;
   }
  
   class EventDescriptor {
       List<EventHandlerInterface> handlers = new Vector<EventHandlerInterface>();
       String eventName;
       String targetMethodName;
       Method method;
   }

   class EventBusInvocationHandler implements InvocationHandler {

       @Override
       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
           if ("hashCode".equals(method.getName())) {
               return EventBus.this.hashCode();
           }
           EventDescriptor eventDescriptor = events.get(method.getName());
           for (EventHandlerInterface presenter : eventDescriptor.handlers) {
               Method presenterMethod = presenter.getClass().getMethod(eventDescriptor.targetMethodName, eventDescriptor.method.getParameterTypes());
               presenterMethod.invoke(presenter, args);
           }
           return null;
       }
   }
}

The Application class

In Mvp4g you define the entry point (or use the mvp4g built in entry point) to bootstrap the framework. In JVM, we create an Application class that does the initialization. While Mvp4g uses Gin, in the JVM we use Guice to do the injection stuff. There's nothing special to it, if you had a GinModule in Mvp4g, you can create a GuiceModule, and provide its injector to the EventBus above.

The Application contains and instantiates the GuiceModule, the EventBus and the mock views, and then calls EventBus#bindView method to give the views to the EventBus.

That's almost all, now you are able to instantiate your application, and play with it through the mock views. You might also directly call events from your test code.

GWT Service invocations

Well, if your application calls GWT services as well, you have to do some hacking about it. The GWT RPC implementation is not symmetric, which means that for example the Readers/Writers (Marshallers) are different on the client and server side. The stream written with a server side writer can only be read by a reader on the client side, not on the server side (classes: com.google.gwt.user.server.rpc.impl.(Client|Server)SerializaionStream(Writer|Reader) ).

Fortunately I wasn't the first to want to call a GWT RPC service from JVM, and there's a project called gwt-syncproxy. It can create sync and async proxies for you as well. I forked in my local workspace and added some functionality to support performance monitoring transparently (see below).

There was still a small issue. As I use this stuff from test code, I have to make sure that all async service invocations finish before I do my assertions in my tests. To achieve this, I extended gwt-syncproxy a little further, and added a little code that keeps track of invocations in each thread (and 'child'-threads), so that the test code can call a waitForInvocations() method before going on to the assertions.

I18n

When I added i18n to the application, my tests suddenly failed :) . It was because while in GWT, it creates and implementation of the message interface, in the JVM we have to replace it with something. Anyway, the localized strings are not so important during the tests, I don't call assertions textual content. However, there has to be an object with the interface. I love creating proxies, so here it is:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import com.google.gwt.i18n.client.LocalizableResource.Key;
import com.google.gwt.i18n.client.Messages.DefaultMessage;

/**
* A class to implement com.google.gwt.i18n.client.Messages derived interfaces when running in a JVM
*/
public class MessagesFactory {
  
   public static <T extends com.google.gwt.i18n.client.Messages> T createInstance(Class<T> cls) {
       return (T) Proxy.newProxyInstance(MessagesFactory.class.getClassLoader(), new Class [] { cls },
               new MessagesInvocationHandler(cls));
   }
  
   protected static class MessagesInvocationHandler<T extends com.google.gwt.i18n.client.Messages>
               implements InvocationHandler {
      
       protected Class<T> cls;
      
       public MessagesInvocationHandler(Class<T> cls) {
           this.cls = cls;
       }

       @Override
       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
           if (method.isAnnotationPresent(DefaultMessage.class)) {
               return method.getAnnotation(DefaultMessage.class).value();
           }
           if (method.isAnnotationPresent(Key.class)) {
               return method.getAnnotation(Key.class).value();
           }
           return method.getName();
       }
   }
}

And in the Guice module, I have to bind it manually:

bind(MyMessages.class).toInstance(MessagesFactory.createInstance(MyMessages.class));

Performance logging

Once I got the taste of using and extending everything in strage ways, I also added performance monitoring on the client side for GWT RPC invocations.

To proxy requests on the client side, I found a solution by Nathan Williams. You can declare in your gwt.xml for which service interfaces you want to use the proxy and then use the bind method when creating the service to pass it an AsyncInvocationHandler that will be called before the actual invocation, and on success and failure. I also extended the gwt-syncproxy in my workspace to support these invocation handlers, and thus I can have my performance data from the integration and load tests too.

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.

Friday, October 1, 2010

Sun Glassfish and Oracle XE Distributed Transactions (XA)

So, we're using Glassfish v2.1.1, currently with Oracle 10g XE, running on a Java 6 runtime, using ojdbc14.jar. And we wanted to use distributed transactions :) We are also using JBoss Seam 2.2, but that's unrelevant, fortunately.

We configured the connection pool to use oracle.jdbc.xa.client.OracleXADataSource, and we disabled 'Return non-transactional connections' of course. When we tried to access the database from our Seam-connected web tier, the following exceptions came up:

[#|2010-10-01T12:09:12.383+0200|INFO|sun-appserver2.1|javax.enterprise.system.container.ejb|_ThreadID=22;_ThreadName=httpSSLWorkerThread-8091-1;|
javax.ejb.EJBException: nested exception is: javax.transaction.SystemException: org.omg.CORBA.INTERNAL: JTS5031: Exception [org.omg.CORBA.INTERNAL:   vmcid: 0x0  minor code: 0 completed: Maybe] on Resource [rollback] operation.  vmcid: 0x0  minor code: 0  completed: No
javax.transaction.SystemException: org.omg.CORBA.INTERNAL: JTS5031: Exception [org.omg.CORBA.INTERNAL:   vmcid: 0x0  minor code: 0 completed: Maybe] on Resource [rollback] operation.  vmcid: 0x0  minor code: 0  completed: No
 at com.sun.jts.jta.TransactionManagerImpl.rollback(TransactionManagerImpl.java:350)
 at com.sun.enterprise.distributedtx.J2EETransactionManagerImpl.rollback(J2EETransactionManagerImpl.java:1150)
 at com.sun.enterprise.distributedtx.J2EETransactionManagerOpt.rollback(J2EETransactionManagerOpt.java:433)
 at com.sun.ejb.containers.BaseContainer.completeNewTx(BaseContainer.java:3801)
 at com.sun.ejb.containers.BaseContainer.postInvokeTx(BaseContainer.java:3619)
 at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:1388)
 at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:1325)
 at com.sun.ejb.containers.EJBLocalObjectInvocationHandler.invoke(EJBLocalObjectInvocationHandler.java:205)
 at com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate.invoke(EJBLocalObjectInvocationHandlerDelegate.java:127)
javax.ejb.EJBException: nested exception is: javax.transaction.SystemException: org.omg.CORBA.INTERNAL: JTS5031: Exception [org.omg.CORBA.INTERNAL:   vmcid: 0x0  minor code: 0 completed: Maybe] on Resource [rollback] operation.  vmcid: 0x0  minor code: 0  completed: No
 at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:1395)
 at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:1325)
 at com.sun.ejb.containers.EJBLocalObjectInvocationHandler.invoke(EJBLocalObjectInvocationHandler.java:205)
 at com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate.invoke(EJBLocalObjectInvocationHandlerDelegate.java:127)
[#|2010-10-01T14:20:10.992+0200|WARNING|sun-appserver2.1|javax.enterprise.system.core.transaction|_ThreadID=18;_ThreadName=httpSSLWorkerThread-8091-1;_RequestID=4131aa28-3401-4edd-bf90-54f605bcbb8e;|JTS5041: The resource manager is doing work outside a global transaction
oracle.jdbc.xa.OracleXAException
 at oracle.jdbc.xa.OracleXAResource.checkError(OracleXAResource.java:1120)
 at oracle.jdbc.xa.client.OracleXAResource.start(OracleXAResource.java:249)
 at com.sun.gjc.spi.XAResourceImpl.start(XAResourceImpl.java:222)
 at com.sun.jts.jta.TransactionState.startAssociation(TransactionState.java:305)
 at com.sun.jts.jta.TransactionImpl.enlistResource(TransactionImpl.java:205)
 at com.sun.enterprise.distributedtx.J2EETransaction.enlistResource(J2EETransaction.java:607)
 at com.sun.enterprise.distributedtx.J2EETransactionManagerImpl.enlistResource(J2EETransactionManagerImpl.java:372)
 at com.sun.enterprise.distributedtx.J2EETransactionManagerOpt.enlistResource(J2EETransactionManagerOpt.java:144)
 at com.sun.enterprise.resource.SystemResourceManagerImpl.enlistResource(SystemResourceManagerImpl.java:98)
 at com.sun.enterprise.resource.PoolManagerImpl.getResource(PoolManagerImpl.java:216)
 at com.sun.enterprise.connectors.ConnectionManagerImpl.internalGetConnection(ConnectionManagerImpl.java:337)
 at com.sun.enterprise.connectors.ConnectionManagerImpl.allocateConnection(ConnectionManagerImpl.java:189)
 at com.sun.enterprise.connectors.ConnectionManagerImpl.allocateConnection(ConnectionManagerImpl.java:165)
 at com.sun.enterprise.connectors.ConnectionManagerImpl.allocateConnection(ConnectionManagerImpl.java:158)
 at com.sun.gjc.spi.base.DataSource.getConnection(DataSource.java:108)
 at org.hibernate.connection.DatasourceConnectionProvider.getConnection(DatasourceConnectionProvider.java:92)
 at org.hibernate.jdbc.ConnectionManager.openConnection(ConnectionManager.java:446)
 at org.hibernate.jdbc.ConnectionManager.getConnection(ConnectionManager.java:167)
 at org.hibernate.jdbc.AbstractBatcher.prepareSelectStatement(AbstractBatcher.java:145)
 at org.hibernate.id.SequenceGenerator.generate(SequenceGenerator.java:96)
 at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:122)
 at org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:49)
 at org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:154)
 at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:110)
 at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:61)
 at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:646)
 at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:620)
 at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:624)
 at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:220)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)
 at org.jboss.seam.persistence.EntityManagerInvocationHandler.invoke(EntityManagerInvocationHandler.java:46)
 at $Proxy263.persist(Unknown Source)

When switching to ojdbc5.jar or ojdbc6.jar, a further detail came in the logs. The exception above doesn't explain the cause of the OracleXAException (in OracleXAResource.start). This seems to be fixed in later drivers, so in the logs we can see:

java.sql.SQLException: ORA-06550: line 1, column 13:
PLS-00201: identifier 'JAVA_XA.XA_START_NEW' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

Searching for this I got completely mislead. Forum entries say that there is no XA support in the XE version of 10g. Indeed, when I tried the whole stuff with Oracle 11g, it worked fine.

I wanted to try the stuff out myself to see how these XA handling looks like on the API level. I created a J2SE native app following the explanation and the code samples found here http://archive.devx.com/java/free/articles/dd_jta/jta-2.asp and here http://download.oracle.com/docs/cd/B14117_01/java.101/b10979/xadistra.htm. And they worked fine, on Oracle 10g XE, with ojdbc14.jar. So obviously, the 10g XE version does support XA.

But why is it then that under Glassfish, it tries to use some fancy JAVA_XA package? It's my habit to jump into the source code of anything I can find, and even read the Eclipse class view showing the JVM level code. So I found that from my J2SE stuff, a T4CXAConnection is returned from the OracleXADataSource, but somewhy under glassfish, it is OracleXAConnection. So why is this difference? Browsing the binary of the OracleXADataSource class I found two suspicious properties named useNativeXA and thinUseNativeXA. And indeed, under glassfish, they were both false, despite their default values of useNativeXA=false, thinUseNativeXA=true.

Searching for these properties I found some explanations here http://download-west.oracle.com/docs/cd/B19306_01/java.102/b14355/xadistra.htm#BGBBHCFC and here http://openesb-users.794670.n2.nabble.com/Definition-of-Oracle-XA-datasources-td4461045.html. So, the Glassfish admin console proposes a default value of the useNativeXA=false, and setting this property also disables thinUseNativeXA. So we either remove this property, or set it to true (the additional properties page of the connnection pool properties), and it'll work fine.

So, after all, I should just have read "Oracle® Database JDBC Developer's Guide and Reference" to be aware of this feature in the jdbc driver :) . Hope it'll help you guys.

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.

Monday, February 15, 2010

An Ajax4JSF bug

The other day I had this exception:

15:16:47,296 ERROR [[Faces Servlet]] Servlet.service() for servlet Faces Servlet threw exception
java.lang.NullPointerException
 at org.ajax4jsf.org.w3c.tidy.Node.trimInitialSpace(Node.java:946)
 at org.ajax4jsf.org.w3c.tidy.Node.trimSpaces(Node.java:1012)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl$ParseInline.parse(ParserImpl.java:1125)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl.parseTag(ParserImpl.java:203)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl$ParseRowGroup.parse(ParserImpl.java:2809)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl.parseTag(ParserImpl.java:203)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl$ParseTableTag.parse(ParserImpl.java:2629)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl.parseTag(ParserImpl.java:203)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl$ParseInline.parse(ParserImpl.java:1587)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl.parseTag(ParserImpl.java:203)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl$ParseBody.parse(ParserImpl.java:978)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl.parseTag(ParserImpl.java:203)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl$ParseHTML.parse(ParserImpl.java:486)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl.parseDocument(ParserImpl.java:3409)
 at org.ajax4jsf.org.w3c.tidy.Tidy.parse(Tidy.java:363)
 at org.ajax4jsf.org.w3c.tidy.Tidy.parse(Tidy.java:261)
 at org.ajax4jsf.org.w3c.tidy.Tidy.parseDOM(Tidy.java:604)
 at org.ajax4jsf.webapp.tidy.TidyParser.parseHtmlByTidy(TidyParser.java:182)
 at org.ajax4jsf.webapp.tidy.TidyParser.parseHtml(TidyParser.java:265)
 at org.ajax4jsf.webapp.FilterServletResponseWrapper.parseContent(FilterServletResponseWrapper.java:594)
 at org.ajax4jsf.webapp.BaseXMLFilter.doXmlFilter(BaseXMLFilter.java:367)
 at org.ajax4jsf.webapp.BaseFilter.handleRequest(BaseFilter.java:290)
 at org.ajax4jsf.webapp.BaseFilter.processUploadsAndHandleRequest(BaseFilter.java:388)
 at org.ajax4jsf.webapp.BaseFilter.doFilter(BaseFilter.java:515)
 at org.jboss.seam.web.Ajax4jsfFilter.doFilter(Ajax4jsfFilter.java:56)
 at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
 at org.jboss.seam.web.LoggingFilter.doFilter(LoggingFilter.java:60)
 at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
 at org.jboss.seam.servlet.SeamFilter.doFilter(SeamFilter.java:158)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
 at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
 at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:230)
 at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
 at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:179)
 at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:84)
 at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
 at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
 at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:157)
 at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
 at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:262)
 at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)
 at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
 at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:446)
 at java.lang.Thread.run(Unknown Source)

I was using JSF 1.2_13-b01-FCS with RichFaces 3.3.2.SR1. The problem turned out to be with the generated HTML content which Ajax4JSF was sending as a response. We were using a4j:repeat to generate a HTML table, and one row of this table was shown using an AJAX request. However, the default rendering behaviour of a4j:outputPanel is the using of some element (span or div, I don't remember which is the actual default), and the html tidy code above had problems with handling those elements between table rows. You just have to define layout="none" for the outputPanel, then it works fine.

The bug is since then reported: https://jira.jboss.org/jira/browse/AJSF-158