Wednesday, October 27, 2010

ADF Region as Source of Partial Trigger

In my previous post, I describe a way on how to expose the state represented by the pageFlowScope object of a bounded task flow (BTF) to the outside world. Now, if there's an event inside the BTF that changes it exposed state, then the dependent outside component needs to refresh as well, and the idea is for it to have a partialTriggers property that point to the region where the BTF resides.

The problem is, the events that changes the state of the task flow are actually coming from the command button components and not the region. To have a hook on these events, then you need have to combine the relative Id of the region and the Id of the command button relative to the containing region like "r1:ctb1". Below is a code snippet from sample app on my previous post.

      <af:form id="f1">
        <af:panelStretchLayout id="psl1" topHeight="50px">
          <f:facet name="center">
            <af:region value="#{bindings.samplestatefulbtftaskflow1.regionModel}"
                       id="r1"/>
          </f:facet>
          <f:facet name="top">
            <af:panelGroupLayout id="pgl1" layout="horizontal">
              <af:toolbar id="t1">
                <af:commandToolbarButton text="Approve" id="ctb1"
                                         partialTriggers="r1:ctb1 r1:ctb2 r1:ctb3"
                                         disabled="#{sessionScope.region1StateHolder['editMode'] eq 'true'}"
                                         actionListener="#{viewScope.mainForm.approve}"/>
              </af:toolbar>
              <af:outputText value="This toolbar button disables and enables depending on the ('editMode') state of the bounded task flow below:" id="ot1"/>
            </af:panelGroupLayout>
          </f:facet>
        </af:panelStretchLayout>
      </af:form>
But in the case of a dynamic region wherein you have no idea of the Ids of the contained command button components then the solution above cannot apply.

We need to step back a little bit and rethink the solution we have above. Since we cannot reference the Ids of the components which we don't have idea about for in the case of a dynamic region, then we should only have a hook on the region itself like "r1". We are left with no choice but to modify our BTFs so that when there's an internal action event that modifies the potentially exposed state of the current BTF, then we also raise a dummy region event as follows:

    private void broadcastAsRegionEvent(FacesEvent event) {
        RichRegion region = findImmediateContainingRegion(event);
        if (region != null) {
            AttributeChangeEvent dummyRegionEvent =
                new AttributeChangeEvent(region, "dummy", null, null);
            region.broadcast(dummyRegionEvent);
        }
    }
    private RichRegion findImmediateContainingRegion(FacesEvent event) {
        UIComponent comp = event.getComponent();
        while (!(comp instanceof RichRegion)) {
            comp = comp.getParent();
        }
        if (comp instanceof RichRegion) {
            return (RichRegion)comp;
        }
        return null;
    }
Below are some sample action listeners that calls the broadcastAsRegionEvent() method above:
    public void edit(ActionEvent actionEvent) {
        //some other operations here

        getPageFlowScope().put(EDIT_MODE, true);

        //workaround so that simply referencing the region
        //as partial trigger will work
        //in real app, need to confirm if the BTF is indeed used as region
        broadcastAsRegionEvent(actionEvent);
    }

    public void cancel(ActionEvent actionEvent) {
        //some other operations here
        getPageFlowScope().put(EDIT_MODE, false);
        broadcastAsRegionEvent(actionEvent);
    }

Wheew! I have the feeling that I have gone too far off :-( . This just started on the challenge of disabling a parent page component when my bounded task flow is on edit mode. Guys, comments and feedback are very much appreciated.

You can download the sample application from here.


Tuesday, October 26, 2010

Bounded Task Flows as Components with Exposed State or Attributes

Overview

Bounded Task Flows (BTFs) can be considered a breakthrough in again bringing the concept of web component reuse to a new level. BTFs can be package into ADF Library JAR files and placed into to a reusable component repository, in which they can be applied in multiple projects and/or applications. This document describes a way on how to make BTFs a more full-pledged interactive component by incorporating them with state and/or attributes.

Problem Description

BTFs have a way to communicate with other BTFs and their containing page through contextual events in which they can pass data as part of the event payload. They can also be navigated from outside through "queueActionEventInRegion()" method exposed by the RichRegion component. But BTF has a major shortcoming as a component, and that is, it does not have an exposed state or attributes.

Solution Description

Undeniably, pageFlowScope is where the internal state and attributes of BTFs are usually kept. As a matter of fact, if you define input parameters, JDeveloper will automatically create the value expressions which are in pageFlowScope. This pageFlowScope is unique per instance of the BTF and will last long from an instance initialization up to a return activity, fault, or explicit detachment(like setting the region with a different TaskFlowId), but is not accessible outside the BTF. This solution provides a way on how the attributes of a bounded task flow can be optionally exposed to other components of the page beyond the BTF boundary.

The User Experience

Figure 1: The main page "Approve" button is enabled when the bounded task flow is not on editMode.
Figure 2: The "Approve" button on the main page is disabled when the bounded task flow was on edit mode.

Artifacts

Exposing the BTF attributes represented by the BTF pageFlowScope can be done in three steps:
  1. Define a string input parameter that will hold a unique value expression provided by the user.
  2. Set the value expression with the pageFlowScope object in the initializer of the BTF.
  3. Set the value expression to null in the finalizer of the BTF to ensure objects are released.

The details of the steps are as follows:
  1. Define a string input parameter that will hold a unique value expression provided by the user.
    Figure 3: Input parameter definition of the string that represents a value expression.
    PageFlowScope is unique per instance of BTF, so it is the responsibility of the user(a developer) of the BTF to ensure that he is passing a unique externalStateValueHolder string value expression per region so that the region pageFlowScopeObjects will not collide which can lead to unexpected results.
  2. Set the value expression with the pageFlowScope object in the initializer of the BTF.
        public void taskInit() {
            //EXTERNAL_STATE_HOLDER is a string constant with value "externalStateHolder"
            String expression =
                (String)getPageFlowScope().get(EXTERNAL_STATE_HOLDER);
            if (expression != null) {
                JSFUtils.setManagedBeanValue(expression, getPageFlowScope());
            }
        }
    
  3. Set the value expression to null in the finalizer of the BTF to ensure objects are released.
        public void taskCleanUp() {
            String expression =
                (String)getPageFlowScope().get(EXTERNAL_STATE_HOLDER);
            if (expression != null) {
                //to ensure pageFlowScope is released upon unload of BTF
                JSFUtils.setManagedBeanValue(expression, null);
            }
        }
    
    Figure 4: Initializer and finalizer definition of the bounded task flow
Below are the other relevant artifacts in the prototype:
  • Region model definition
        <taskFlow id="samplestatefulbtftaskflow1"
                  taskFlowId="/WEB-INF/sample-stateful-btf-task-flow.xml#sample-stateful-btf-task-flow"
                  activation="deferred"
                  xmlns="http://xmlns.oracle.com/adf/controller/binding">
          <parameters>
            <parameter id="externalStateHolder"
                       value="sessionScope.region1StateHolder"/>
          </parameters>
        </taskFlow>
    
  • Toolbar button disabled attribute
        <af:commandToolbarButton text="Approve" id="ctb1"
               partialTriggers="r1:ctb1 r1:ctb2 r1:ctb3"
               disabled="#{sessionScope.region1StateHolder['editMode'] eq 'true'}"
               actionListener="#{viewScope.mainForm.approve}"/>
    

Prototype

You can download the sample application from here.

Resources

  • Initiate Control Flow Within A Region From Its Parent Page Functional Pattern

Caveat

Care should be taken when you are exposing the BTFs pageFlowScope to the outside world (outside the boundaries of the BTF), because it could lead your BTFs to unexpected inconsistent state. The solution just illustrate the concept and it can easily be tweak to exposed a much restricted Map object instead of the pageFlowScope.

Wednesday, October 20, 2010

ADF Faces RC: Resolving "'BracketSuffix' returned null"

javax.el.PropertyNotFoundException: Target Unreachable, 'BracketSuffix' returned null
 at com.sun.el.parser.AstValue.getTarget(AstValue.java:96)
 at com.sun.el.parser.AstValue.getType(AstValue.java:56)
This is my story:
I have a string status attribute which is represented by a single character in the database but should be displayed in my pages as meaningful words as follows:
  • A -Active
  • P -Pending
  • U -Undefined
  • N -New
So I created a Constants class with a map attribute that holds the appropriate key-value pairs and defined this class as an application scoped managed bean.
I refer this in the page as follows:
<af:panelLabelAndMessage label="#{bindings.status.hints.label}"
                                     id="plam1">
  <af:outputText value="#{nisConstants.statusMap[bindings.status.inputValue]}" id="ot1"
                             clientComponent="true"/>                       
</af:panelLabelAndMessage>

And then I started encountering the error above. This is pretty hard to identify if you have a lot of expression in the page that has brackets, and I hope that the ADF Development team will improve on defining the error by including in the trace the expression that causes the problem.

Below is my analysis:
I easily avoided such error by initializing my status attribute to some default value like below:
    private String status = "N";

Cheers!

Wednesday, October 13, 2010

Looking Up SOAServiceInvokerBean from a Different JVM

I am writing this up before I will forget and hit this again...
javax.naming.CommunicationException [Root exception is java.rmi.UnmarshalException: failed to unmarshal class java.lang.Object; nested exception is: 
 java.lang.ClassNotFoundException: oracle.integration.platform.blocks.sdox.ejb.api.SOAServiceInvokerBean]
 at weblogic.jndi.internal.ExceptionTranslator.toNamingException(ExceptionTranslator.java:74)
....
Caused by: java.rmi.UnmarshalException: failed to unmarshal class java.lang.Object; nested exception is: 
 java.lang.ClassNotFoundException: oracle.integration.platform.blocks.sdox.ejb.api.SOAServiceInvokerBean
 at weblogic.rjvm.ResponseImpl.unmarshalReturn(ResponseImpl.java:244)
 at weblogic.rmi.cluster.ClusterableRemoteRef.invoke(ClusterableRemoteRef.java:348)
...
Caused by: java.lang.ClassNotFoundException: oracle.integration.platform.blocks.sdox.ejb.api.SOAServiceInvokerBean
 at weblogic.application.internal.AppClassLoaderManagerImpl.loadApplicationClass(AppClassLoaderManagerImpl.java:135)
 at weblogic.common.internal.ProxyClassResolver.resolveProxyClass(ProxyClassResolver.java:68)

I normally do not encounter this error when I look up the SOAServiceInvokerBean inside a simple java client, but when I tried to look this up inside one of my backing bean then I start hitting it again.

The following is an excerpt from the Developer's Guide for Oracle SOA Suite
36.4 Designing an SDO-Based Enterprise JavaBeans Client to Invoke Oracle SOA Suite To invoke an SDO - Enterprise JavaBeans service from Enterprise JavaBeans, you must use the client library. Follow these guidelines to design an Enterprise JavaBeans client.
  • Look up the SOAServiceInvokerBean from the JNDI tree.
  • Get an instance of SOAServiceFactory and ask the factory to return a proxy for the Enterprise JavaBeans service interface.
  • You can include a client side Enterprise JavaBeans invocation library (fabric-ejbClient.jar or the fabric-runtime.jar file located in the Oracle JDeveloper home directory or Oracle WebLogic Server) in the Enterprise JavaBeans client application. For example, the fabric-runtime.jar file can be located in the JDev_Home\jdeveloper\soa\modules\oracle.soa.fabric_11.1.1 directory.
If the Enterprise JavaBeans application is running in a different JVM than Oracle SOA Suite, the Enterprise JavaBeans application must reference the ejbClient library.

It says that to invoke the SOAServiceInvokerBean from a different JVM then you need to have the "fabric-ejbClient.jar" in your classpath, but "fabric-ejbClient.jar" is nowhere to be found. Searching on the same directory suggested above, we can find a "fabric-client.jar", and yes you are right that it can serve the purpose.

In summary, to look up and invoke the SOAServiceInvokerBean from a different JVM, you must reference the "fabric-client.jar" located in JDev_Home\jdeveloper\soa\modules\oracle.soa.fabric_11.1.1 directory.

Cheers!

Friday, October 8, 2010

Using GMail as Mail Server for Oracle SOA Suite 11g Notifications

Lucas Jellema has a good post about configuring SOA Suite 11g for sending email notifications with Google Mail, but unluckily, as he also noted, it was not working anymore. Nevertheless, that post demonstrated that Gmail could work as SMTP server for Oracle SOA and inspired me to dig more. In this post, I will share my configuration and the steps that I have made to have a working GMail SMTP server. The following are my suggested steps:
  1. Configure Workflow Notification Properties
  2. Configure Email Driver Properties
  3. Import the GMail's SSL certificate into Java keystore
  4. Remove "-Djavax.net.ssl.trustStore=%WL_HOME%\server\lib\DemoTrust.jks" in setDomainEnv.cmd

Configure Workflow Notification Properties

Right-click soa-infra > SOA Administration > Workflow Notification Properties
Set Workflow Notification to "All" or "Email".
Replace the "admin@javasoadev.com" and "action@javasoadev.com" with your own gmail address. You could replace both with just a single account. Don't be alarmed with my email address configuration that doesn't end with "gmail.com" because I have subscribe to Google Apps where I could have a customized email address.

Configure Email Driver Properties

Please see my configuration below which is presented as is for your reference.
Note that I am not using cleartext password but an encrypted indirect password as follows:

Import the GMail's SSL certificate into Java keystore

Follow the instructions on the following site up to the step of importing of the certificate to the keystore. Needless to say, import the certificate to the same JAVA_HOME that runs your weblogic server instance. http://confluence.atlassian.com/display/JIRA042/Using+Gmail+as+a+JIRA+Mail+Server

Remove "-Djavax.net.ssl.trustStore=%WL_HOME%\server\lib\DemoTrust.jks" in setDomainEnv.cmd

It seems that this command argument is affecting the way Weblogic locates trust as described in the following document: How WebLogic Server Locates Trust. With that argument, weblogic is not looking trust into JDK cacerts keystores.

Quick Testing...

Goto SOA Infrastructure>Service Engine>Human Workflow

Click "Notification Management" tab then click "Send Test Notification"... Enter something like the following values. Ensure that you gave a valid "Send To" email address.
Voila! Email is received accordingly...
Cheers and Thanks to Lucas!