Thursday, January 14, 2010

EJB3 Weblogic 10 and backward compability

(http://blog.sunfire.nu/2008/12/ejb3-weblogic-10-and-backward.html)

Been trying to develop EJB3s that will be hosted on a Weblogic 10 server, and some client applications that will be running on Weblogic 8.
Took me a while to figure out how to get the JDNI names and stuff right for the EJBs as well as how to be compatible with EJB2 spec (since Weblogic 8 and JDK1.4 doesn't support EJB3).

There are of course a million tutorials and examples to find using google, so that's were I started. Found one here to start with but I ended up getting ClassCastExceptions. Looking at the EJB3 specs I found the mappedName attribute of the @Stateless annotation, and after setting that I got my client code working. Since mappedName is vendor-specific the information will not be applicable if you're running a different application server.

package ejb;
public interface TestRemote {
public String echo(String s);
}
----------------------
package ejb;
import javax.ejb.*;

@Stateless(mappedName="Base")
@Remote(TestRemote.class)
public class TestBean implements TestRemote {
public String echo(final String s) {
return "echo: " + s;
}
}

The clients can now perform a lookup like this:

Properties p = new Properties();
p.put("java.naming.factory.initial","weblogic.jndi.WLInitialContextFactory");
p.put("java.naming.provider.url", "t3://localhost:7001");
p.put("java.naming.security.principal", "weblogic");
p.put("java.naming.security.credentials", "weblogic");
InitialContext ctx = new InitialContext(p);
TestRemote test = (TestRemote) ctx.lookup("Base#ejb.TestRemote");
test.echo("Hello");

After getting my clients to access the EJB3s (the clients were using WLS10 libraries and JDK6) and calling methods on them I moved on to get them to work with WLS8 clients.
This turned out to involve a little more effort, suddenly it wasn't enough to write POJOs and annotations.
First of all, we need to create a EJB2 equivivalent business interface for our EJB3 interface (TestRemote) that must extend javax.ejb.EJBObject and contain the same methods that must throw java.rmi.RemoteException.

package ejb;
public interface TestRemote2 extends javax.ejb.EJBObject {
public String echo(String s) throws java.rmi.RemoteException;
}

Second, we need a Home interface that the EJB2 client can lookup and use to create the EJB:

package ejb;
import java.rmi.RemoteException;
import javax.ejb.*;
public interface TestRemoteHome extends EJBHome {
public TestRemote2 create() throws CreateException, RemoteException;
}

Notice that the create method returns the EJB2 interface (of course...).
Finally, we update the bean class itself:

package ejb;

import javax.ejb.*;

@Stateless(mappedName="Base")
@Remote(TestRemote2.class)
@RemoteHome(TestRemoteHome.class)
public class TestBean implements TestRemote {
public String echo(final String s) {
return "echo: " + s;
}
}

Ok, we changed the Remote annotation to contain the EJB2 interface instead and we added the RemoteHome annotation as well to specify the Home interface to publish. Notice that we still implement TestRemote (not the EJB2 interface) so at least we don't have to worry about RemoteExceptions here.

The client code looks like this:

// Context setup as before
Object o = ctx.lookup("Base#ejb.TestRemoteHome");
TestRemoteHome home = (TestRemoteHome)PortableRemoteObject.
narrow(ctx.lookup("Base#ejb.TestRemoteHome"),
TestRemoteHome.class);
TestRemote21 test = home.create();
test.echo("Hello");

That's all there is to it.

Update after comments:
To enable both EJB3 and EJB2 beans you just add the interfaces in the @Remote annotation like this:
@Remote({TestRemote2.class, TestRemote.class})

No comments: