Thursday 29 September 2011

ADF TreeModel utility methods

In all my latest dealings with the RichTree component in ADF I've found the following three (3) utility methods to be very handy (let's say they were implemented in a utility class called: Utility):

  // Returns the last RowKey object from the given set
  private static Object lastRowKeyFromSet(RowKeySet set) {
    Iterator it = set.iterator();
    Object rowKeyPointer = null;
    while (it.hasNext()) {
      rowKeyPointer = it.next();
    }
    return rowKeyPointer;
  }




  // Returns a String value of the given node attribute as found in the specified TreeModel at the specified row index.
  public static String retrieveNodeAttributeFromRowIndexForTreeModel(String attributeName, int rowIndex,
    TreeModel treeModel) {
    String attr = null;

    if(rowIndex > -1) {
      Object rowData = treeModel.getRowData(rowIndex);
      JUCtrlHierNodeBinding nodeBinding = (JUCtrlHierNodeBinding) rowData;

      if(nodeBinding != null) {
        attr = (String) nodeBinding.getAttribute(attributeName);
      }
    }
    return attr;
  }


  // Returns a String value of the given node attribute as found in the specified TreeModel at the last row key
  // as per the specified row key set.
  public static String retrieveNodeAttributeFromRowKeySetForTreeModel(String attributeName, RowKeySet set,
    TreeModel treeModel) {
    String attr = null;

    if ((set != null) && (set.size() > 0)) {     
      Object currentRowKey = Utility.lastRowKeyFromSet(set);     
      JUCtrlHierNodeBinding nodeBinding = (JUCtrlHierNodeBinding) treeModel.getRowData(currentRowKey);     
      if (nodeBinding != null) {       
        attr = (String) nodeBinding.getAttribute(attributeName);       
      }
    }   
    return attr;
  }


Hope you find it handy!

Wednesday 21 September 2011

OraFormsFaces Forms Servlet URL set dynamically by java code

I've recently had to integrate with Oracle Forms as part of an ADF Web Application. Of course, the framework of choice is OraFormsFaces. But, I got particularly annoyed with the HtmlFormRenderer and the way it looks up the value of the Forms Servlet URL. This renderer currently only looks at the Java Namespace in the JNDI Context of the Web Application (the recommended way to configure OraFormsFaces as per the developer guide; environment entry in the web.xml, which gets loaded into the namespace: java:comp/env) AND the Input Parameters of the ServletContext. Nothing wrong with this approach, but there is no allowed way to change this value after the initial configuration has been done (I mean the JEE spec does not allow change of these values after start-up of your app). Hence, no way around this!

Well, I wanted to change the Forms Servlet URL value dynamically as per the environment within which my Web Application gets deployed to. So, I extended the HtmlFormRenderer of OraFormsFaces to also lookup this value from an additional place as to the JNDI Context and the Servlet Context (input parameters). My extension allows a lookup from the accessible attributes of the Servlet Context. Hence, one can implement a ServletContextListener which can set this attribute key-value pair on successful initialization of the Servlet Context, by simply making use of the setAttribute()-method on the Servlet Context interface.
On the consumer side of the Servlet Context, the extended HtmlFormRenderer will then simply do a getAttribute() with the standard OraFormsFaces-key for the Forms Servlet URL.

I thought this solves the problem in the neatest way possible, as I only had to change the "faces-config.xml", which resides in the OraFormsFaces JAR file, to point to my custom HtmlFormRenderer instead of the standard one shipped with OraFormsFaces.

I've included code sniplets of my java code from both the ServletContextListener and the HtmlFormRenderer, respectively:

(ServletContextLister)
  public void contextInitialized(ServletContextEvent ctxEvent) {
    ServletContext servletCtx = ctxEvent.getServletContext();
    servletCtx.setAttribute(OraFormsFacesUtil.FORMS_SERVLET_URL_KEY,
      OraFormsFacesUtil.getFormsServletURL());
  }


(HtmlFormRenderer)
  public class MyHtmlFormRenderer extends HtmlFormRenderer {
    .

    .
    public String getFormsServletURL(FacesContext facesContext) {
      String url = null;   
      ServletContext servletContext = (ServletContext)
           facesContext.getExternalContext().getContext();
      url = (String) servletContext.getAttribute(
          OraFormsFacesUtil.FORMS_SERVLET_URL_KEY);
      if (url != null) {
        String result = url != null ? url.trim() : null;
        return result;     
      } else {
        return super.getFormsServletURL(facesContext);
      }
    }
  }
You'll notice I've used a helper class (OraFormsFacesUtil) to keep the Forms Servlet URL attribute key in one place and to implement the functionality which determines what the Forms Servlet URL value must be.

Currently, I'm thinking a better solution would be to implement the above solution similar to the way the FormsCredentialProvider configuration works. This way, you will also simply specify a class name in the web.xml of your Web Application which adheres to a given interface (i.e. FormsServletURLProvider, declaring a method with signature: String getFormsServletURL(), for example) which can then be used to retrieve the URL programmatically. Then the implementation can rely on configuration or java code; for the developer to decide.

OraFormsFaces using a reusable Task Flow to load Forms in a common way

I've implemented a common/reusable Task Flow to load Oracle Forms in a generic way in order to function as part of an ADF Web Application in an integrated fashion.

Below is the source-xml of the Task Flow I've implemented (oraFormsFaces-tfd.xml):
<?xml version="1.0" encoding="windows-1252" ?>
<adfc-config xmlns="http://xmlns.oracle.com/adf/controller" version="1.2">
  <task-flow-definition id="oraFormsFaces-tfd">
    <default-activity>loadForm</default-activity>
    <input-parameter-definition id="__2">
      <name>formName</name>
      <value>#{pageFlowScope.formName}</value>
      <class>java.lang.String</class>
      <required/>
    </input-parameter-definition>
    <input-parameter-definition id="__3">
      <name>ipAddress</name>
      <value>#{pageFlowScope.ipAddress}</value>
      <class>java.lang.String</class>
      <required/>
    </input-parameter-definition>
    <input-parameter-definition id="__4">
      <name>formDescription</name>
      <value>#{pageFlowScope.formDescription}</value>
      <class>java.lang.String</class>
      <required/>
    </input-parameter-definition>
    <managed-bean id="__1">
      <managed-bean-name>oraFormsFacesBean</managed-bean-name>
      <managed-bean-class>com.myflow.common.view.oraformsfaces.OraFormsFacesBean</managed-bean-class>
      <managed-bean-scope>request</managed-bean-scope>
    </managed-bean>
    <view id="loadForm">
      <page>/oraformsfaces/loadForm.jspx</page>
    </view>
  </task-flow-definition>
</adfc-config>


You'll notice the following from the above listing:
  1. I've defined three (3) input parameters to my Task Flow for it was required by the specific use case. An interesting thing to mention is that I had to set these input parameters dynamically. This is actually done in a very simple way by calling the above Task Flow with an URL as follow: "/faces/adf.task-flow?adf.tfId=oraFormsFaces-tfd&adf.tfDoc=/WEB-INF/oraformsfaces/oraFormsFaces-tfd.xml", and then simply appending the input parameters as request parameters to the URL as such: "?formName=MYORAFM&ipAddress=10.4.5.6.7&formDescription=My Oracle Form".
  2. I've implemented a backing bean (OraFormsFacesBean) within the Task Flow to facilitate the representation of user friendly information to the user AND to expose the values to be passed to OraFormsFaces and Forms as available attributes retrievable from the backing bean. As you'll see later these attributes are referenced from the loadForm.jspx page.
  3. The page, /oraformsfaces/loadForm.jspx, facilitates the load of the actual OraFormsFaces Form component. Please see the source of this page in the listing below:
loadForm.jspx
<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
          xmlns:off="http://commit-consulting.com/OraFormsFaces/tags">
  <jsp:directive.page contentType="text/html;charset=UTF-8"/>
  <f:view>
    <af:document title="SPIF" id="loadFormDoc" maximized="true">
      <af:pageTemplate id="p_tmpl" viewId="/templates/defaultFormTemplate.jspx">
        <f:facet name="mainContent">
          <af:panelGroupLayout id="pgl2" styleClass="AFStretchWidth">
            <off:form formModuleName="#{pageFlowScope.formName}" id="oraFormsFacesForm" clipApplet="true"
                      autoClipTop="menu" loadingImage="preset5" autoSize="true" uniqueAppletKey="piet">
              <off:formParameter id="userID" value="#{oraFormsFacesBean.username}"/>
              <off:formParameter id="companyCode" value="#{oraFormsFacesBean.companyCode}"/>
              <off:formParameter id="clientIP" value="#{pageFlowScope.ipAddress}"/>
              <off:formParameter id="companyCode" globalName="companyCode" value="#{oraFormsFacesBean.companyCodeCode}"/>
              <off:formParameter id="userid" globalName="userid" value="#{oraFormsFacesBean.username}"/>
            </off:form>
          </af:panelGroupLayout>
        </f:facet>
        <f:attribute name="formBean" value="#{oraFormsFacesBean}"/>
        <f:attribute name="displayLinksFacet" value="false"/>
      </af:pageTemplate>
    </af:document>
  </f:view>
</jsp:root>



Please note how I've used the OraFormsFacesBean as backing bean to provide the values:
  • formName
  • username (send through as 'userID')
  • companyCode (also send through as 'companyCode')
  • ipAddress (send through as 'clientIP')
Also important to note, is that there are two (2) Global Forms Parameters (going directly through to Oracle Forms) and three (3) OraFormsFaces Parameters of which I only passed through the latter in order for it to get used by the off_lib.pll (PLS/SQL library) provided as part of the OraFormsFaces installation.

For completeness sake, I'll explain what I did in the off_lib.pll PL/SQL library. First observe the script sniplet below:
    elsif eventName = 'initapplet' then
          -- add code here that is execute when the applet is first started
          -- in a browser session. This is not re-executed when the applet is
          -- reused on subsequent pages.
          --null;
          v_params := offParams.getParameters;
          v_userID := offParams. getParamValue(v_params, 'userID');
          v_companyCode := offParams. getParamValue(v_params, 'companyCode');
          v_clientIP := offParams. getParamValue(v_params, 'clientIP');
          APPSYS.AUTH.prepare_session(v_userID,v_companyCode,v_clientIP);

You'll notice that I've added a few statements in the 'initapplet'-clause of the "eventName-if-statement" as found in the handleGlobalEvent(eventName in varchar2) procedure in off_lib.pll. These statements simply extract the parameters which were sent through and then send them through to an implemented Stored Procedure that will do the necessary preparation work for the Oracle Forms to function in an integrated fashion. For example, this procedure will set the session identifier on the database session to the value of 'userID' for auditing purposes.

I hope someone finds this as useful as I did!
 

Using an Environment Info Provider to switch between Data Sources

I had to deal with a requirement where the Application Module had to choose between two configured Data Sources depending on some Principal as configured in the Security Context's Subject. The way to do it is as follow:

1. Implement a class that extends oracle.jbo.common.ampool.EnvInfoProvider
2. Override the "public Object getInfo(String infoType, Object env)"-method
    i.e. body for the above method to be overridden below:

    if(EnvInfoProvider.INFO_TYPE_JDBC_PROPERTIES.equals(infoType)) {
      String dsName = DEFAULT_DS_JNDI_NAME; // the default Data Source' JNDI Name    
      if(ADFContext.getCurrent().getSecurityContext().getSubject() != null) {
        Subject sbj = ADFContext.getCurrent().getSecurityContext().getSubject();
        // utitlity method to extract the FcoCode principal from the given subject
        String fcoCode = AMUtil.interrogateSubjectForFinanceCompanyPrincipal(sbj);       
        if(fcoCode != null) {
            // calling private method to determine the appropriate Data Source JNDI Name
            // based on FcoCode
          dsName = this.determineDataSourceFromFcoCode(fcoCode);        
        }       
      }     
      ((Hashtable) env).put(Configuration.JDBC_DS_NAME, dsName);
    }
    return null;


3. Configure the Application Module to make use of the implemented Environment Info Provider by
    specifying your custom class as value for property: "jbo.envinfoprovider".
    (see screenshot below)




 

Friday 9 September 2011

OraFormsFaces Java trusted/untrusted warning message

Problem statement:
The OraFormsFaces' applet contains code which is trusted and also untrusted. So, as per Java's help pages: "As of the Java SE 6 Update 19 release, when a program contains both signed and unsigned components, a warning dialog is raised. " Therefore, if it is the case with your scenario as it was with mine you'll see the following warning dialog when you load a OraFormsFaces Form component in ADF:

Resolution:
You have to open the Java Console from Control Panel (in the case of Windows) and change the security settings in order to make the display of the warning message go away.
To access the Java Control Panel goto, Start menu > Control Panel > Java Control Panel > Advanced > Security. See the Mixed code heading towards the bottom of the Java Control Panel.
You can tick any of the two "Enable - hiding warning..." options provided. In my case I picked: "Enable - hide warning and run with protections." I've included a screenshot of the Java Console Security options as it is displayed in Windows 7:

I hope you've found it useful! :-)