Sunday, 21 June 2009

Tomcat - load & bind an external naming context

In my previous post Injecting JBoss EJB3 beans into Tomcat6, I used Tomcat's ability to bind EJB's to the local JNDI context by specifying JBoss' NamingContextFactory and the URL to the EJB. However in the process of using it, I discovered a problem with Stateful Session Beans.

I was getting NoSuchEJBException - either after a redeploy to JBoss, or calling the @Remove annotated method on the session bean. A lot of debugging later, I discovered that the cause was that Tomcat was initially storing a ejb ref (org.apache.EjbRef) in the context and when first accessed it looks up the EJB using the factory and then replaces the reference in the context with the instance of the EJB. Every subsequent lookup in the context returns the same instance.

This however breaks the JEE5 contract:
FROM JSR 220: NOTE: When a stateful session bean is looked up or otherwise
obtained through the explicit JNDI lookup mechanisms, the container must
provide a new stateful session bean instance, as required by the Java EE
specification (Section “Java Naming and Directory Interface (JNDI) Naming
Context” [12]).
I initially tried a few hacks to stop Tomcat 6 storing the instance and only keeping the ref, but I was worried about the lag every I required a new instance. Each time it creates a context factory, EJB factory etc to look up the EJB. If you were manually doing the lookup using a custom service locator, you would create a singleton service locator and stores a initial context with the remote app server's details pumped in. Thus everytime you wanted an instance a single call to the existing context would be all that was required.

My solution I felt was to bind an external context naming tree to the local naming tree. That way you could reference any external object easily on the external context with just a @EJB ref. The resulting patch is located here, it is against Tomcat 6.0.20. You can also download my precompiled binary:
catalina.jar
And the source:
apache-tomcat-6.0.20-src

To use it create a context.xml:
<Context>
   <!-- Binds the remote context tree starting at myear to local name java:/comp/env/ejb. -->
   <Resource name="ejb" type="javax.naming.Context" factory="org.jnp.interfaces.NamingContextFactory" URL="jnp://apphost:1099/myear"/>
</Context>
Next if you want to reference a bean which has a @Stateful(mappedName="myear/myBean"), using the following in your JSF/contains managed bean:
public class TomcatManagedBean
{
   @EJB(name="ejb/myBean")
   private MyBeanRemote session;

Thursday, 18 June 2009

Injecting JBoss EJB3 beans into Tomcat6

Injecting EJB's into a managed bean (a bean spawned by appserver, of a JSF managed bean) inside an application server is simple:
public class AppManagedBean
{
   @EJB(mappedName="ejb/myBean")
   private MyBeanRemote session;
The container looks up the mapped bean and injects it. Easy.

However in tomcat it gets a little messy. Tomcat doesn't document how to inject them. It assumes something annotated with @EJB is in the context somewhere and tries to look it up.
It has documented how to lookup ejb's by defining entries in your context.xml or server.xml. But there isn't anything documented for defining EJB's. Looking at the soure it has support for <Ejb/> references but no real clue on how to configure it. So after stepping through the code I came up with the following soloution:

My environment is JBoss 5 running remotely (on localhost). A seperate instance of Tomcat 6. In it I have defined a session bean:
@Stateful(mappedName="userSession")
public class UserSessionBean implements UserSessionRemote 
This bean is used by a JSF backing bean (i.e. a managed bean):
public class TomcatManagedBean
{
   @EJB(name="ejb/myBean")
   private MyBeanRemote session;
In order for this bean to bound to the context, you need to configure Tomcat. There are 2 ways: in the server.xml or in the context.xml
I find that putting it in META-INF/context.xml is the easiest, as it gets reloaded nicely in Eclipse. The name="ejb/myBean" corresponds to the name attribute in the <Ejb/> below. In the URL is the appserver name, port, then the mapped name (it could be myProject/ejb/bean or whatever you feel like) - as long as it matched the mappedName on the EJB.
<Context>
   <!-- Makes Tomcat manage the ejb for you. -->
   <Ejb name="ejb/myBean" type="Session" factory="org.jnp.interfaces.NamingContextFactory" URL="jnp://localhost:1099/userSession" />
</Context>
Update 2010/02/02: Correct the mappedName in AppManagedBean

Sunday, 14 June 2009

Change user name in eclipse

The code comment templates are handy for inserting the author of a class, which is handy in rather large projects. For example, here is my template for types(classes):
/**
* @author ${user}
*
* ${tags}
*/

However under windows (and probably *nix as well) it just inserts the current user name. But I want it always to display my real name - which is more meaningful when employees come and go.

You can change it by passing this argument on the comment line:
You need to put it at the end of your command line.
eclipse.exe -vmargs -Duser.name="You real name"

Friday, 12 June 2009

A few good Guice Tutorials

I came across these rather good Guice tutorials in trying to find a way to integrate Google Guice in an application server (EJB3).

Part 1: a quick intro.
Part 2: method interceptors.

This was the tricky one, how to inject EJB's which are created by the container without having to have every class extend a base class with a @PostConstruct annotated method to do the injecting. The solution was to use the new interceptors introduced into JEE 5.
Part 3 – Integrating Guice with EJB
If you want a little background, see EJB Interceptors.

Saturday, 6 June 2009

CommandButton not calling action when using immediate and rendered

I came across a problem with using a custom command component, that when using immediate it wasn't calling the action I had bound to it. I started resolving the issue and discovered my decode code wasn't being called. I had create a custom button, cannibalising and extending the original CommandButton component. Due to my lack of understanding of setting up renderkits, I gave up and hacked in a renderer:
public class ImageButton extends HtmlCommandButton
{
   private ImageButtonRenderer render = new ImageButtonRenderer();
   
   @Override
   public void decode(FacesContext context)
   {
      render.decode(context, this);
   }
   
   @Override
   public void encodeBegin(FacesContext context) throws IOException
   {
      render.encodeBegin(context, this);
   }
This resulted in decode not always working. I came across this nice summary of how to write a custom component and altered my code accordingly. Decode worked but the action was still not called. Thought the cause was just with immediate="true". So I swtiched to using a standard commandButton for testing.
<h:inputhidden value="#{testBean.add}" immediate="true" />
<h:commandbutton value="Delete" immediate="true" rendered="#{!testBean.add}" />
However the command button was still not being decoded, as I thought immediate on the attribute should work but for the 2nd time I've misunderstood how it works for input components. Immediate on input components only changes priority of validation, it doesn't cause them to update the model any earlier.

UIComponentBase.processDecodes:1018 calls isRendered() - which returns false as the Update Model Values phase hasn't been applied. Which skips the decode of the the component. Which clearly isn't right. Whether it was rendered or not should be remembered from the Render phase during the decode(Apply Request Values) phase. In the following phases it should be calculated from the EL binding. This to me seems a bug in the specification or the implementation.

So how to use a model value for the rendered attribute for a button with immediate on?

The answer use Tomahawk's saveState tag. The state is maintained on the server side, and as long as it is put before the other tags that need it, it will restore the model/attribute to the same value it was after the Render phase during the Apply Request Values phase. Thus when it goes to decode the button, the same values that were used to calculate the rendered during the previous rendering to create this page are used.
<t:savestate value="#{testBean.add}" />
<h:commandbutton value="Delete" immediate="true" rendered="#{!testBean.add}" />

ClassCastException with PortableRemoteObject.narrow() EJB2/EJB3

After switching to JBoss 5 and recreating my EAR project inside eclipse, I started getting ClassCastException:
java.lang.ClassCastException
at com.sun.corba.se.impl.javax.rmi.PortableRemoteObject.narrow(PortableRemoteObject.java:229)
at javax.rmi.PortableRemoteObject.narrow(PortableRemoteObject.java:137)
Object obj = initialContext.lookup(ejbReferenceComponent);
MyEJBRemoteHome home = javax.rmi.PortableRemoteObject.narrow(obj, MyEJBRemoteHome.class);

After looking around, the cause of it is how the app server determines whether to give you a EJBHome instance which you have to use .narrow() then .create() to get a reference to the bean for EJB2 vs returning a proxy to the bean itself for EJB3. JBoss has some information on referencing EJB3 from EJB2 environments and vice-versa.

The summary generally is if the application.xml has the EJB2 header then it gives you an EJBHome interface.
If the ear's ejb-jar has the EJB3 header then it gives you a proxy to the bean itself (no need for narrow/create).

The lack of a more intelligent error from PortableRemoteObject is a bit annoying. However here is a generic solution for cases when your switching between environments:
public <T, S extends EJBHome> T lookupEJB(String ejbReferenceComponent, Class<S> homeClass, Class<T> ejbClass)
throws NamingException, IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException,
NoSuchMethodException
{
log.debug("{}", initialContext.getEnvironment());
java.lang.Object remote = initialContext.lookup(ejbReferenceComponent);

if (ejbClass.isInstance(remote))
return (T) remote;

S home = (S) PortableRemoteObject.narrow(remote, homeClass);

return (T) home.getClass().getMethod("create", (Class[]) null).invoke(home, (Object[]) null);
}
Of course the simplest approach is to convert it all to EJB3 and you won't have to worry :)