Thursday, January 3, 2013

Inject EJBs into non ejb-jar's

When CDI is enabled (existing beans.xml in resources/META-INF), all EJBs of a *.war or *.ear can be injected with the @Inject Annotation (JSR 330).
The Injection Point can be defined in any Java Class, not only EJBs, because all classes are Managed Beans when CDI is enabled. So far that works fine, but not for Classes packed as a .jar under /lib.
The Problem occurred while implementing a plugin solution for a *.ear packaged project.

Plugin:
 public class PluginA implements IPlugin {  
   
      @Inject SomeClass anotherPOJO; //works fine  
      @Inject SomeService someStatelessEJB; //doesn't work  
 …  
 }   

By the way, defining replaceable Plugins can be easily done with @Inject @Any javax.enterprise.inject.Instance<T>, where the CDI Container locates all Implementations and injects them in the javax.enterprise.inject.Instance Object, which offers an Iterator to loop through all Plugins found. The same can be achieved by injecting the javax.enterprise.inject.spi.BeanManager and iterate through the Set of Beans (Bean<T>) returned from its getBeans() Method, passing either the EL Name (String based Qualifier) or Type and Qualifier.  

Because the Plugins don't need any Container Managed Transaction or Security, defining them as EJBs (@Stateless, Stateful or @Singleton) would not bring any advantage, so they are implemented as POJOs and packed as plain .jar files under /lib. 
Now, all CDI Managed Beans can be injected in the Plugin Implementations, but it doesn't work for EJBs like that. The EJB DI only works for Classes inside an ejb-jar or *.war. but not for POJOs located in a .jar file under /lib.
To inject EJBs nevertheless i wrote a Factory Class with Producer Method which does a JNDI lookup for the desired EJB and returns it.

EJBFactory:
 public class EJBFactory {  
   
    String jndi =   
    "java:global/myApp/myModule/"  
    + "SomeServiceImpl!com.foo.SomeService";  
   
    @Produces  
    public SomeService getSomeService() {  
      try {  
         Context c = new InitialContext();  
         return (SomeService) c.lookup(jndi);  
      } catch (NamingException e) {  
         //log  
      } catch(ClassCastException ce) {  
         //log  
      }  
      return null;  
    }  
 …  
 }  

Note:
The Producer Method can also throw a checked Exception (in case the lookup or Casting failed -> throws NamingException, ClassCastException) which will be caught by the CDI Container and re thrown as javax.enterprise.inject.CreationException, which then will cause an abortion of the Deployment. I decided to just return null and check that when the Service is actually needed to avoid Exceptions during Deployment.

With that Factory, located anywhere inside the Application, directly or also as a *.jar packed lib, the EJB will be injected using @Inject SomeService someStatelessEJB;
To avoid Ambiguous Dependencies Issues when Injecting the EJBs also outside of the Plugins, a Qualifier is needed. 
Let me explain that a little more detailed. 
The SomeService Interface is Implemented by an EJB (@Stateless) and used also inside the Application. It is Injected in other EJBs or POJOs with @Inject, and that works fine because the InjectionPoints are located inside an *.ear or .*war. When the EJBFactory also offers a Producer Method for SomeService Injection, the CDI Container finds 2 Dependencies for that SomeService Type, one is the actually implemented SomeServiceImpl (not available for the Plugins) and the other Dependency is the EJBFactory's Producer Method. To solve that we simply need to add an Qualifier to the Producer Method to be able to differentiate between them.

The Qualifier is defined as Annotation annotated with @Qualifier and looks like this:

Qualifier:
 @Qualifier  
 @Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})  
 @Retention(RetentionPolicy.RUNTIME)  
 public @interface EJBResource {}  

Now the Qualifier has to be used for both, the Producer Method and the Plugins InjectionPoints.

Extend the EJBFactory:
 
public class EJBFactory {  
   
    String jndi =   
    "java:global/myApp/myModule/"  
    + "SomeServiceImpl!com.foo.SomeService";  
   
    @Produces  
    @EJBResource
    public SomeService getSomeService() {  
      try {  
         Context c = new InitialContext();  
         return (SomeService) c.lookup(jndi);  
      } catch (NamingException e) {  
         //log  
      } catch(ClassCastException ce) {  
         //log  
      }  
      return null;  
    }  
 …  
 }  

and the Plugin:
 public class PluginA implements IPlugin {  
   
      @Inject SomeClass anotherPOJO; //works fine  
      @Inject @EJBResource SomeService someStatelessEJB; //works now  
 …  
 }   

Thats all!





   

No comments :

Post a Comment