Question

I have an application which is part JavaEE (the server side) part JavaSE (the client side). As I want that client to be well architectured, I use Weld in it to inject various components. Some of these components should be server-side @EJB.

What I plan to do is to extend Weld architecture to provide the "component" allowing Weld to perform JNDI lookup to load instances of EJBs when client tries to reference them. But how do I do that ?

In other worrds, I want to have

on the client side

public class ClientCode {
    public @Inject @EJB MyEJBInterface;
}

on the server-side

@Stateless
public class MyEJB implements MyEJBInterface {
}

With Weld "implicitely" performing the JNDI lookup when ClientCode objects are created. How can I do that ?

Was it helpful?

Solution

Basically, doing so requires write a so-called portable CDI extension.

But, as it is quite long and requires a few tweaks, let me explain it further.

Portable extension

Like weld doc explains, first step is to create a class that implements the Extension tagging interface, in which one will write code corresponding to interesting CDI events. In that precise case, the most interesting event is, to my mind, AfterBeanDiscovery. Indeed, this event occurs after all "local" beans have been found by CDI impl.

So, writing extension is, more opr less, writing a handler for that event :

public void loadJndiBeansFromServer(
        @Observes AfterBeanDiscovery beanDiscovery, BeanManager beanManager)
        throws NamingException, ClassNotFoundException, IOException {
    // Due to my inability to navigate in server JNDI naming (a weird issue in Glassfish naming)
    // This props maps interface class to JNDI name for its server-side
    Properties interfacesToNames = extractInterfacesToNames();

    // JNDI properties
    Properties jndiProperties = new Properties();
    Context context = new InitialContext();
    for (Entry<?, ?> entry : interfacesToNames.entrySet()) {
        String interfaceName = entry.getKey().toString();
        Class<?> interfaceClass = Class.forName(interfaceName);
        String jndiName = entry.getValue().toString();
        Bean<?> jndiBean = createJndIBeanFor(beanManager, interfaceClass, jndiName, jndiProperties);
        beanDiscovery.addBean(jndiBean);
    }
}

Creating the bean is not a trivial operation : it requires transforming "basic" Java reflection objects into more advanced weld ones (well, in my case)

private <Type> Bean<Type> createJndIBeanFor(BeanManager beanManager, Class<Type> interfaceClass,
        String jndiName, Properties p) {
    AnnotatedType<Type> annotatedType = beanManager
            .createAnnotatedType(interfaceClass);
    // Creating injection target in a classical way will fail, as interfaceClass is the interface of an EJB
    JndiBean<Type> beanToAdd = new JndiBean<Type>(interfaceClass, jndiName, p);
    return beanToAdd;
}

Finally, one has to write the JndiBean class. But before, a small travel in annotations realm is required.

Defining the used annotation

At first, I used the @EJB one. A bad idea : Weld uses qualifier annotation method calls result to build hashcode of bean ! So, I created my very own @JndiClient annotation, which holds no methods, neither constants, in order for it to be as simple as possible.

Constructing a JNDI client bean

Two notions merge here.

  • On one side, the Bean interface seems (to me) to define what the bean is.
  • On the other side, the InjectionTarget defines, to a certain extend, the lifecycle of that very bean.

From the literature I was able to find, those two interfaces implementations often share at least some of their state. So I've decided to impelment them using a unique class : the JndiBean !

In that bean, most of the methods are left empty (or to a default value) excepted

  • Bean#getTypes, which must return the EJB remote interface and all extended @Remote interfaces (as methods from these interfaces can be called through this interface)
  • Bean#getQualifiers which returns a Set containing only one element : an AnnotationLiteral corresponding to @JndiClient interface.
  • Contextual#create (you forgot Bean extended Contextual, didn't you ?) which performs the lookup :

    @Override
    public T create(CreationalContext<T> arg0) {
        // Some classloading confusion occurs here in my case, but I guess they're of no interest to you
    
        try {
            Hashtable contextProps = new Hashtable();
            contextProps.putAll(jndiProperties);
            Context context = new InitialContext(contextProps);
            Object serverSide = context.lookup(jndiName);
            return interfaceClass.cast(serverSide);
        } catch (NamingException e) {
            // An unchecked exception to go through weld and break the world appart
            throw new LookupFailed(e);
        }
    }
    

And that's all

Usage ?

Well, now, in my glassfish java client code, I can write things such as

private @Inject @JndiClient MyRemoteEJB instance;

And it works without any problems

A future ?

Well, for now, user credentials are not managed, but I guess it could be totally possible using the C of CDI : Contexts ... oh no ! Not contexts : scopes !

OTHER TIPS

Section 3.5 of the CDI spec should help out. You may want to use some of the properties on the EJB annotation as well. Also, (probably don't need to tell you this) make sure you have JNDI set up correctly on the client to reference the server, and pack any of the needed interfaces into your client jar.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top