Inject EJB to ADF Managed Bean

Current 11g version of ADF framework will not inject fields/methods having @EJB annotation if the managed bean scope is PageFlow, BackingBean or View. However, dependencies will be injected for default JSF scope like Request, Session and Application. It’s because the managed beans with default JSF scope are created by “BeanBuilder” calss while the rest is created by “ManagedBeanFactory”, the “ManagedBeanFactory” doesn’t inject dependencies. In order to avoid looking up EJB in managed beans, after reading the ADF source code, an EL resolver “ELResolverWithEJBInjection” could solve this.

The resolver class ELResolverWithEJBInjection.java has two main parts, the first part (please refer the code below) overrides the getValue method. This resolver is part of the resolver chain, the ADF framework will call each resolver’s getValue method until the variable is resolved, the getValue method could call “elContext.setPropertyResolved(true);” to indicate it’s resolved. This resolver will only inject EJB for PageFlowScope, BackingBeanScope and ViewScope which all are subclass of ScopeMap. Some code below is from ManagedBeanFactory.java because we need to follow what the framework does.

public class ELResolverWithEJBInjection extends MapELResolver {
    ...
    public Object getValue(ELContext elContext, Object base, Object var) {
        ScopeMap scope = (ScopeMap)(base instanceof ScopeMap ? base : null);
        if (scope == null || scope.containsKey(var))
            return null;
        Object bean = null;
        try {
            ManagedBeanFactory mbf = ManagedBeanFactory.getInstance();
            ManagedBean managedBean = mbf.getManagedBeanInCurrentPageFlow((String)var);
            ManagedBeanScopeType expectedScope = (ManagedBeanScopeType)getManagedBeanScopeType.invoke(scope, null);
            if (((Boolean)shouldIncludeHigherScopes.invoke(scope, null)) &&
                (managedBean == null || !managedBean.getScope().equals(expectedScope))) {
                managedBean = mbf.getManagedBeanInAdfPageFlow((String)var);
            }
            if (managedBean != null && managedBean.getScope().equals(expectedScope)) {//only resolve for target scopes
                bean = newInstance.invoke(mbf, managedBean);//create instance of the manged bean
                if (bean != null) {
                    scope.put((String)var, bean);
                    EJBInjector.inject(bean);//Inject the EJB dependencies
                    //Following will invoke the @PostConstruct
                    AnnotationUtils.runPostConstructIfSpecified(bean, managedBean.getName(), managedBean.getScope());
                    elContext.setPropertyResolved(true);//indicate it's resolved
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return bean;
    }
}

The second part is the injection part (please refer code below), the “inject” method will do the EJB injection.

public class ELResolverWithEJBInjection extends MapELResolver {
    ...
    static class EJBInjector {
        ...
        public static void inject(Object o) throws IllegalAccessException, InvocationTargetException, NamingException {
            List<AccessibleObject> inj = null;
            if (!processed.containsKey(o.getClass().getName())) {
                inj = new ArrayList<AccessibleObject>(2);
                //following is to detect the @EJB annotation
                for (Field field : o.getClass().getDeclaredFields()) {
                    if (field.getAnnotation(EJB.class) != null) {
                        field.setAccessible(true);
                        inj.add(field);
                    }
                }
                for (Method method : o.getClass().getDeclaredMethods()) {
                    if (method.getAnnotation(EJB.class) != null) {
                        method.setAccessible(true);
                        inj.add(method);
                    }
                }
                processed.put(o.getClass().getName(), inj.isEmpty() ? null : inj);
            }
            if (inj != null || (inj = processed.get(o.getClass().getName())) != null) {
                for (AccessibleObject i : inj) {
                    if (i instanceof Method)
                        ((Method)i).invoke(o,
                                           ctx.lookup(i.getAnnotation(EJB.class).mappedName() + "#" + ((Method)i).getParameterTypes()[0].getName()));
                    else if (i instanceof Field)
                        ((Field)i).set(o,
                                       ctx.lookup(i.getAnnotation(EJB.class).mappedName() + "#" + ((Field)i).getType().getName()));
                }
            }
        }
        ...
    }
}

To use this resolver, add this highlighted line into the faces-config.xml:

<faces-config version="1.2" xmlns="http://java.sun.com/xml/ns/javaee">
  <application>
    <default-render-kit-id>oracle.adf.rich</default-render-kit-id>
    <el-resolver>cp.adf.resolver.ELResolverWithEJBInjection</el-resolver>
  </application>
</faces-config>

Limitations & Improvements:
1) Some reflections are on the methods of classes from package “oracle.adfinternal.*”, Oracle may change these methods name/signature anytime.
2) Current injection could be replaced by Spring injection without any Spring xml configuration. In the “EJBInjector”, we could do following to register managed bean:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(o.getClass());
ctx.refresh();

We could use following to inject the bean:

ctx.getAutowireCapableBeanFactory().autowireBean(o);

If you don’t want to register the bean on the fly, you could use “ctx.scan(String…)” to scan target packages at startup, this¬†programmatic approach doesn’t require any Spring xml configuration either.

To me, the limitation here is unfortunately too severe to use it in production, however, it’s a good practice and you could use it if you could bear the risk. The source code here:¬†ELResolverWithEJBInjection.