What you may need to know while calling Application Module methods from Java EE components such as EJB and MDB

Update on 11 Dec 2014

While discussing some weird issues centered around invocation of AM from non ADF client such as EJB, MDB etc, ADF development team pointed our that more safer way of calling AM is through ADF binding as discussed here: https://blogs.oracle.com/jdevotnharvest/entry/when_to_use_createrootapplicationmodule_in_oracle_adf
----------------------------------------------------------------------------------------------------------------------

A couple of years back I blogged about Calling Application Module Methods from EJB and MDB. Recently I learned that when a non web client invokes methods defined in EJB or MDB that uses Application Module, it may results in some unexpected ClassCastException  for DefLocaleContext. The error stack may look like as listed below.
Caused by: java.lang.ClassCastException: oracle.jbo.common.DefLocaleContext cannot be cast to oracle.jbo.common.DefLocaleContext at oracle.jbo.common.DefLocaleContext.getInstance(DefLocaleContext.java:84) 

The reason is that the current request gets the ADFContext cached by the previous thread. As these threads may have different class loaders, casting class loaded by one loader to the other result in weird ClassCastException. This post contains a solution for such cases. Here you go...

 1. If you are accessing EJB(such as a SessionBean) from a web UI, then configure ServletADFFilter in web.xml to intercept all the requests that access EJB using ADF BC. The ServletADFFilter sets up ServletADFContext properly at the start of the request so that ADF BC can access the right context object during execution. If you use ADF binding, then this step is not required because the ADFBindingFilter configured in web.xml does the same job on initializing ADFContext.

<filter>
  <filter-name>ServletADFFilter</filter-name>
  <filter-class>oracle.adf.share.http.ServletADFFilter</filter-class>
</filter>
 

<filter-mapping>
  <filter-name>ServletADFFilter</filter-name>
   <url-pattern>/*</url-pattern>
  <dispatcher>REQUEST</dispatcher>
  <dispatcher>FORWARD</dispatcher>
</filter-mapping>


2. If you are invoking EJB in non web UI context, then you will not have the luxury of using Servlet filter for initializing the context. For example calling EJB methods from a desktop client or from some batch program. In such cases you may need to manually initialize the ADF context. A possible solution is to define an interceptor class for the EJB session bean and add the ADFContext initialization code in the AroundInvoke method in the interceptor class.

An example of using interceptor with EJB is here:
Define the interceptor as shown below:

public class ADFContextInterceptor {
    public ADFContextInterceptor() {
        super();
    }
    @AroundInvoke
    public Object manageADFContext(InvocationContext ctx) throws Exception {
        ADFContext currentADFContext = null;
        try {
            System.out.println(" - manageADFContext - entry ");
            currentADFContext =
                    ADFContext.initADFContext(null, null, null, null);
            return ctx.proceed();
        } finally {
            System.out.println(" - manageADFContext - exit ");
            ADFContext.resetADFContext(currentADFContext);
        }

    }
}

Now configure your Session Bean to use this interceptor as shown in the following example so that all method invocations will be routed through your interceptor code.

@Stateless(name = "DemoSessionEJB",
           mappedName = "EJBWithADFBCApp-EJBModel-DemoSessionEJB")
@Interceptors(value = ADFContextInterceptor.class)
public class DemoSessionEJBBean implements DemoSessionEJB,
                                           DemoSessionEJBLocal {
 //... implementation go here...
}


3. If the technology that you use do not support any interceptors around method  invocation as discussed above, then you can try adding the ADFContext handling code before and after of your custom code that access AM method as listed below:

public void someBusinessMethod() throws Exception {
        ADFContext currentADFContext = null;
        try {
            currentADFContext =
                    ADFContext.initADFContext(null, null, null, null);

            
             //Your code that access AM and doing business some 
             //actions go here

           
        } finally {
             ADFContext.resetADFContext(currentADFContext);
        }

    }

Note
In case if you see the following  warning in the console while accessing application module from aclient bypassing ADF binding layer, the above solution will help you to get rid of it

WARNING: Automatically initializing a DefaultContext for getCurrent. 
Caller should ensure that a DefaultContext is proper for this use. 

Memory leaks and/or unexpected behaviour may occur if the automatic initialization is performed improperly. 

Download

You can download the sample workspace from here. [Runs with Oracle JDeveloper 11g R1 11.1.1.7.0 + Oracle XE].
See the EJBModel project which uses the interceptor approach discussed in this post in order to initialize ADFContext object.

A note of thanks

Many thanks to my colleague Ricky Frost (Oracle ADF Team) who pointed out a weird logical error in my code while resetting ADF context at the end of invocation. He mentioned that  ADFContext.resetADFContext() should be called unconditionally at the end of the method.  I corrected the same in my code without asking any questions as Ricky knows these things much better than me :)

Comments

  1. Hi Jobinesh,

    I am trying to call AM from an external application by using webservice. I have initialized ADFContext before calling the
    AM but I am getting the Warning "Memory Leaked" , I have absorbed that before calling ADFContext.resetADFContext() I am getting above warning.

    ReplyDelete
  2. If you check ADF sources, calling the method with null arguments:

    ADFContext.initADFContext(null, null, null, null);

    has exactly the same effect as calling:

    ADFContext.getCurrent();

    To make a difference, you would need to pass first argument: ServletContext, but that doesn't make sense in your use case.

    ReplyDelete

Post a Comment