Home > ejb, Glassfish, Java > javax.ejb.NoSuchEJBException after redeploying remote EJBs (JEE6)

javax.ejb.NoSuchEJBException after redeploying remote EJBs (JEE6)

Have you ever tried to remotely lookup an ejb and get NotSuchEJBException when remote ejb gets redeployed (in a EJB 3 environment)?
I did and I had to solve this problem absolutely!

Before proceeding, it’s important that you consider your own ejb usage/architectural scenario in order to understand your problem. i.e.: is your target ejb deployed to the same cluster of your client code jee components? If so, you shouldn’t get such an exception without a clear motivation (is really your target ejb up’n’running?), because application server will handle redeploying “in-same-cluster” transparently!

There is a good Ejb FAQ page for glassfish (also valid for many JEE 6 containers)  that can help you when dealing with such situations. However, the scenario I’m going to provide the solution for is actually the case you lookup an ejb outside your own cluster/instance. What happens behind the scene of an ejb remote call? It’s all about RMI-iiop. However, explaining the protocol is not the purpose of this blog post. If you need to know more about how Glassfish handles this calls , I suggest a quite old but good post about ejb Dynamic Proxies on glassfish.

So…as a starting point, not only a StackOverflow post gave me a great suggestion, but also the solution itself! In fact, Dynamic Proxies applied by Glassfish container seems not to automatically manage “retries” nor the standard specifications allow programmers to specify, in a easy and finer way, other technical aspects such as timeouts (change ORB properties for all components!?!?)   :-(

I will report here the Vetler‘s base points from StackOverflow post simply renaming some stuff and omitting the name of the ejb as @EJB attribute (this will simplify a bit  the “EjbClientInvocationHandler” usage).
Declaring CDI injection resources

public class MyCdiResources {
 @Inject
 MyEjbRemote obj;
 }

The producer makes the looked-up MyEjbRemote reference available via an injection method that delegates to our custom EjbClientInvocationHandler through a proxy.

public class MyEjbProducer {
@EJB
private MyEjbRemote obj;

@Produces
public MyEjbRemote getEjb(final InjectionPoint ip) {
InvocationHandler handler = new EjbClientInvocationHandler(this,MyEjbRemote.class,obj);
return (MyEjbRemote)Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
}
}

So, suppose you have a glassfish-web.xml (or similarly glassfish-ejb-jar.xml) containing:

<ejb-ref>
<ejb-ref-name>mypackage.MyEjbProducer/obj</ejb-ref-name>
<jndi-name>corbaname:iiop:myremotemachine:4837#java:global/my-ear/my-remote-ejb/MyEjb!my.remote.package.MyEjbRemote</jndi-name>
</ejb-ref>

As soon as container loads the producer, it performs jndi remote call and injects MyEjbRemote stub into producer itself. When a CDI resource claims MyEjbRemote, CDI will invoke “getEjb()” method. Now, let’s go deep inside the complete custom implementation of InvocationHandler:


public class EjbClientInvocationHandler implements InvocationHandler {

  private static final Logger log = Logger.getLogger(EjbClientInvocationHandler.class.getName());
  private Object objectContainingEjbRef;
  private Object ejbRef;
  private String jndiNameRef;
  private Class<?> currentClass = null;

  private Field getCurrentClassField(){
    Field[] fz = objectContainingEjbRef.getClass().getDeclaredFields();
    for (Field field : fz) {
      if (field.getGenericType().equals(currentClass)){
        return field;
      }
    }
    throw new IllegalStateException("'"+currentClass.getName()+"' is not a field of '"+objectContainingEjbRef.getClass().getName()+"'"+
        " Class<T> for "+EjbClientInvocationHandler.class.getSimpleName()+" must be a coherent field!!");
  }

  public <T> EjbClientInvocationHandler(Object objectContainingEjbRef, Class<T> ejbRefClass, T ejbRef) {
    this.objectContainingEjbRef = objectContainingEjbRef;
    this.ejbRef = ejbRef;
    currentClass =  ejbRefClass;
    if (currentClass==null){
      throw new IllegalStateException("when instanciating "+EjbClientInvocationHandler.class.getSimpleName()+" you MUST specify the remote interface class as <T> parameter!!");
    }
    //build the ejb-ref-name that container "attaches" to ejb jndi environment. This name is actually bound to a remote reference as
    //we defined through glassfish-*.xml.
    jndiNameRef = "java:comp/env/"+objectContainingEjbRef.getClass().getName()+"/"+getCurrentClassField().getName();
    if (log.isLoggable(Level.FINEST)){
      log.finest("jndiNameRef="+jndiNameRef);
    }
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      log.fine("invoking "+ejbRef.getClass().getName()+"."+method.getName()+"...");
      return method.invoke(ejbRef, args);
    } catch (final InvocationTargetException e) {
      if (e.getTargetException() instanceof NoSuchEJBException) {
        // look up EJB again
        Context ic = new InitialContext();
        if(log.isLoggable(Level.INFO)){
          log.info("looking up again '"+jndiNameRef+"' after got NoSuchEJBException");
        }
        ejbRef = ic.lookup(jndiNameRef);
        //re-assign the new remote stub performing injection at "low level" via reflection (maybe there's a better way via CDI?)
        Field f = getCurrentClassField();
        f.setAccessible(true);
        f.set(objectContainingEjbRef, ejbRef);
        if(log.isLoggable(Level.INFO)){
          log.info("Injection successful of  '"+currentClass.getName()+"' into '"+objectContainingEjbRef.getClass().getName()+"'");
        }
        //now make call again
        return method.invoke(ejbRef, args);               
      }else{
        throw e;
      }
    }

  }
}

As you can see, the code above may be optimized but it simpy works! I’ve tested the above solution in both ejb->remoteejb call and web->remoteejb scenarios with two Glassfish 3.1.2 instances. Hope this helps. Vins

  1. Non c'è ancora nessun commento.
  1. ottobre 13, 2014 alle 13:31
  2. novembre 7, 2014 alle 10:48
  3. agosto 1, 2015 alle 07:08

Lascia un commento

Effettua il login con uno di questi metodi per inviare il tuo commento:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...

%d blogger cliccano Mi Piace per questo: