Wednesday, April 15, 2009

How to call a remote transactional Weblogic EJB from a Java client

My objective here is to show how to call a remote Weblogic stateless EJB from a Java client, then adding the ability to invoke multiple EJBs in a client-controlled transaction.

The code below is very simple and has been tested with BEA Weblogic Application Server 10.0 and developed with the Netbeans 6.1 IDE. The original requirement was to call Weblogic EJBs from Glassfish, using the Weblogic user transactions from the client code. Let's say there is the need to call several EJBs and to make it in a single unit of work, this is not usually a best practice, as client-controlled transactions are usually slower and lead to bad design. The best would be to expose a transactional service from Weblogic which wraps all the calls in one server-side unit of work, but it is not always feasible if we are not in control of the other side and this is also an example how to call a remote EJB from Java, if one removes the transactional code.

Anyway, let's have a look at our sample JEE 5 service (EJB 3), which is deployed in Weblogic:
package com.mt.ejb;
import javax.ejb.Stateless;

@Stateless(mappedName = "CalculatorBean")
public class CalculatorBean implements CalculatorRemote {
        public int add(int a, int b) {
        return a + b;
    }
}



Above is a trivial "hello world" EJB, implementing a Remote interface. The mappedName = "CalculatorBean" is a mandatory parameter for the @Stateless annotation if one wants the EJB to be remotely callable.

I deployed in Weblogic, Netbeans offers the ability to connect to a Weblogic server, even if the plugin has limited functionalities it gives me what I need and allows me to work from my favourite IDE and deploy from there.


Here comes the client code, which is more interesting:

package com.mt.client;

import com.mt.ejb.CalculatorRemote;
import java.util.Hashtable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.UserTransaction;

public class WeblogicTest {

    private final Logger log = Logger.getLogger(WeblogicTest.class.getName());

    public WeblogicTest() {
    }

    public void callRemoteEJB() {
        InitialContext context = null;
        UserTransaction transaction = null;

        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
        env.put(Context.PROVIDER_URL, "t3://localhost:7001");
        env.put(Context.SECURITY_PRINCIPAL, "weblogic");
        env.put(Context.SECURITY_CREDENTIALS, "weblogic");

        try {
            context = new InitialContext(env);
            transaction = (UserTransaction) context.lookup("javax.transaction.UserTransaction");
            if (transaction != null) {
                log.info("in transaction context.");
                try {
                    CalculatorRemote calculator = (CalculatorRemote) context.lookup("CalculatorBean#com.mt.ejb.CalculatorRemote");
                    transaction.begin();
                    int result = calculator.add(3, 5);
                    transaction.commit();
                    log.info("add=" + result);
                } catch (Exception ex) {
                    ex.printStackTrace();
                    try {
                        transaction.rollback();
                    } catch (Exception ex1) {
                        log.log(Level.SEVERE, "Can't rollback transaction.", ex1);
                    }
                }
            } else {
                log.info("transaction context is null.");
            }
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        WeblogicTest test = new WeblogicTest();
        test.callRemoteEJB();
    }
}

It is necessary to add the CalculatorBean EJB jar file and the wlclient.jar to build the above client code. The wlclient.jar can be usually found into folder \server\lib of our Weblogic server.


Let's comment the main code sections.

First, I populate the connection properties:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
env.put(Context.PROVIDER_URL, "t3://localhost:7001");
env.put(Context.SECURITY_PRINCIPAL, "weblogic");
env.put(Context.SECURITY_CREDENTIALS, "weblogic");
I look-up the initial context and get the UserTransaction from Weblogic's JTS:
context = new InitialContext(env);
transaction = (UserTransaction) context.lookup("javax.transaction.UserTransaction");
I lookup and call the remote EJB by enclosing the method invocation in transaction:
CalculatorRemote calculator = (CalculatorRemote) context.lookup("CalculatorBean#com.mt.ejb.CalculatorRemote");
transaction.begin();
int result = calculator.add(3, 5);
transaction.commit();
The lookup string "CalculatorBean#com.mt.ejb.CalculatorRemote" makes the trick, it is the Weblogic syntax to identify the Bean and its Remote interface.

Of course, in this trivial case there are not any transactions, but I wanted to keep the example very simple. If the EJB was transactional, the call made use of the transactional context, while in this case it is simply ignored.