Developer: Java
   DOWNLOAD
 Oracle JDeveloper & ADF
 
   TAGS
java, forms, All
 

 

Integrating Oracle Forms into Oracle ADF Faces


by Wilfred van der Deijl

 

Learn a technique for extending existing Forms applications using Oracle ADF components.

Published June 2007

Oracle Forms has been Web-enabled for years. It is perfectly possible to run Oracle Forms in a Web browser, and in its 2005 Statement of Direction, Oracle indicated that it remains committed to the development and support of Oracle Forms.

On the other hand, there is rapid growth in and adoption of Java/JEE technologies and a general shift toward service-oriented architecture (SOA). With Oracle JDeveloper and Oracle Application Development Framework (ADF), it is possible to build feature-rich JavaServer Faces (JSF) Web applications. This article describes a combination of techniques that enables you to integrate these two worlds.

Oracle ADF is an innovative J2EE development framework available in Oracle JDeveloper. It simplifies Java development by minimizing the need to write code that implements well known design patterns and the application's infrastructure. Oracle ADF provides these implementations as part of the framework.

This article focuses on ADF Faces, one of the components of the ADF Framework. ADF Faces contains a large set of UI components built on top of the standard JSF APIs that leverage the latest technologies—including partial page rendering and Ajax—to provide a rich, interactive user interface. Although this article assumes the use of ADF Faces, the same techniques can also be used to integrate Oracle Forms with other Web technologies, such as PHP, Oracle Application Express, or .Net ASP.

In this article I'll present an extreme example of integrating Oracle Forms and Oracle ADF Faces in such a way that end users won't notice the difference between the Oracle ADF Faces user interface and the pages that incorporate an existing Oracle Forms form. See Figure 1 for a simple example of such integration. Only the multirecord block showing the orders is an existing Oracle Forms form. The rest of the page comprises Oracle ADF Faces components.

Figure 1
Figure 1 Oracle Forms form integrated into Oracle ADF Faces

This example demonstrates several things:

  • Visually, the form is tightly integrated with the Oracle ADF Faces page. The menu, toolbar, and status bar are not shown. This integration changes the user experience from what is typical in Oracle Forms to a more classic Web experience. It enables you to gradually adopt Oracle ADF Faces while still leveraging features of your Oracle Forms applications. In this example, it is easy to replace the Oracle Forms form with an Oracle ADF Faces table without users noticing. For some situations, this extreme integration might be too much and you could leave users with a menu, toolbar, and/or status bar if you like. You could just as well go with a solution in which users stay with the well-known multidocument interface of Oracle Forms, with callouts to Oracle ADF Faces pages for specific tasks.
  • What you cannot clearly see in the screen shot is that only orders for a particular customer are shown. The currently selected customer is on the (pure Oracle ADF Faces) Customers tab, which means that the ID of the selected customer is passed to Oracle Forms to query the appropriate records.
  • Oracle Forms also passes its context back to Oracle ADF Faces. When you scroll through the multirecord Orders block, the detail Oracle ADF Faces table showing the order lines requeries automatically. This proves that the order ID is passed back to Oracle ADF Faces and that Oracle Forms can trigger events (such as the refresh of the detail table) in Oracle ADF Faces. In this example, the Oracle ADF Faces table is on the same page, but it could just as easily have been on a different Web page. The key concept is that Oracle Forms passes context to Oracle ADF Faces.
  • The Oracle ADF Faces page also has a Save Form submit button. That Oracle ADF Faces button sends an event to Oracle Forms to instruct it to perform a commit, which shows that it is possible to raise Oracle Forms events from within Oracle ADF Faces.
  • Another thing this screen shot does not show is the elimination of the Oracle Forms applet startup times. Oracle Forms uses a Java applet in the client browser. Starting this applet takes a couple of seconds. When a user navigates between Oracle ADF Faces pages and some of them include an Oracle Forms form, you don't want the user to experience this startup time for each page that includes such a form.

Including the Applet

The first hurdle is to include the actual Oracle Forms form on an Oracle ADF Faces Web page. See Figure 2 for an overview of how an Oracle Forms form runs in the client browser. The figure shows how, conceptually, Oracle Forms (green block) runs as a Java applet (blue block) on a Web page (yellow block) in the client's browser.

Figure 2
Figure 2 Oracle Forms applet on a Web page

Typically this entire Web page is served by the Oracle Forms servlet. In this concept, you no longer rely on this servlet to supply the necessary HTML. The HTML required to start the applet needs to be incorporated into true Oracle ADF Faces Web pages.

You could create a JSF component to render this HTML and include the component on your Oracle ADF Faces pages. For now, use the easier route of defining a region and reusing it on all of your Oracle ADF Faces pages. First you create a JSPX file that defines the Oracle ADF Faces tags to output the Oracle Forms HTML to the client:

     
<?xml version='1.0' encoding='windows-1252'?>
<af:regionDef var="regionParams" 
              xmlns:f="#"
              xmlns:af="http://xmlns.oracle.com/adf/faces">
  <f:verbatim>
    . . . { actual HTML and JavaScript goes here } . . .
  </f:verbatim>
</af:regionDef>        

Next, register this region in the META-INF/region-metadata.xml file:

     
<component>
  <component-type>com.example.oracleFormRegion</component-type>
  <component-class>oracle.adf.view.faces.component.UIXRegion</component-class>
  <component-extension>
    <region-jsp-ui-def>/regions/oraFormsRegion.jspx</region-jsp-ui-def>
  </component-extension>
  <attribute>
    <attribute-name>formModuleName</attribute-name>
    <attribute-class>java.lang.String</attribute-class>
  </attribute>
</component>   

Finally, you can reuse this definition with <af:region> on every Oracle ADF Faces page that needs to embed an Oracle Forms form:

  
<af:region id="oraFormRegion" regionType="com.example.oracleFormRegion">
  <f:attribute name="formModuleName" value="orders.fmx" />
</af:region>   

The use of <af:regionDef> and <af:region> enables you to define the HTML and JavaScript required to start Oracle Forms only once and reuse it on every Oracle ADF Faces page in which you want to embed an Oracle Forms form.

Inbound JavaScript API

The key requirement is a technique to link Oracle Forms and Oracle ADF Faces. It should be possible for both environments to communicate with each other. This ability is necessary for passing context (such as the ID of the currently selected record) to the other environment and even to raise events in the other technology. You can achieve this with a JavaScript-based API. Oracle ADF Faces can include JavaScript to call into Oracle Forms, and Oracle Forms should be able to execute JavaScript to communicate with Oracle ADF Faces.

The first technique I describe here is use of an inbound JavaScript API that enables Oracle ADF Faces to raise an event in Oracle Forms and thereby execute PL/SQL code in Oracle Forms. A simple example is an Oracle ADF Faces Save button that both commits the Oracle Forms form and submits and saves the Oracle ADF Faces input items on the same page.

Oracle Forms 10g Release 2 and previous versions do not have a native JavaScript API. This is a feature whose introduction is planned with the release of Oracle Forms 11g, but you can implement this feature yourself in Oracle Forms 10g Release 2 (or previous versions) by using the industry-standard LiveConnect API, which is implemented by all major browsers. This API gives JavaScript the ability to call public methods of Java classes. The Oracle Forms applet is a Java class, and if this class exposes a public method, it can be called from JavaScript.

The Oracle Forms applet as shipped by Oracle does not offer a public method, but you can add one yourself. Sub-class the Oracle Forms applet class oracle.forms.engine.Main, and add a public raiseEvent() method. This new public method can be called from the client-side JavaScript and receive a payload (message) to pass on to Oracle Forms (see step 1 in Figure 3):

    
document.formsapplet.raiseEvent('do_key', 'commit_form');        

Figure 3
Figure 3 Inbound JavaScript API

This public raiseEvent() method still needs to trigger code in the active Oracle Forms form. This is not something you can do directly. Fortunately Oracle Forms offers a framework called Pluggable Java Components (PJCs) for doing something like this. A PJC is a JavaBean that sub-classes oracle.forms.ui.VBean, which can be included in an Oracle Forms form. We can create a generic PJC to handle this inbound JavaScript communication. Make sure to embed this PJC in each form. Doing so can be a simple automated task with the Java Development API (JDAPI). The public method from the extended Oracle Forms applet can get a handle on the PJC in the active form and pass the payload on to that PJC (see step 2 in Figure 3):

     
public void raiseEvent(String event, String payload) {
  CommunicatorBean communicator = findFirstCommunicator();
  if (null != communicator) {
    communicator.sendMessageToForms(event, payload);
  }
}        

A PJC can trigger an Oracle Forms trigger called WHEN-CUSTOM-ITEM-EVENT (see step 3 in Figure 3):

public void sendMessageToForms(String event, String payload) {
  try {
    // set properties to pass to Oracle Forms
    mHandler.setProperty(PROP_EVENT, event);
    mHandler.setProperty(PROP_PAYLOAD, payload);
    // trigger WHEN-CUSTOM-ITEM-EVENT trigger in Forms
    CustomEvent ce = new CustomEvent(mHandler, EVENT_MSG_TO_FORMS);
    dispatchCustomEvent(ce);
  } catch (FException e) {
    e.printStackTrace();
  }
}

Finally, you can use PL/SQL code to inspect the payload of the event and act accordingly:

           
declare
  BeanEventDetails  ParamList;
  ParamType         number := text_parameter;
  Event             varchar2(1000);
  Payload           varchar2(1000);
begin
  BeanEventDetails := get_parameter_list(:system.custom_item_event_parameters);
  get_parameter_attr(BeanEventDetails, 'Event',   ParamType, Event);
  get_parameter_attr(BeanEventDetails, 'Payload', ParamType, Payload);
  if event='do_key' then
    do_key(payload);
  end if;
end;            

This completes the path from the client-side JavaScript to the Oracle Forms-side PL/SQL code. It starts with the client-side JavaScript's calling a public method of the extended Oracle Forms applet and passing a payload (step 1). The public method then gets a handle on the PJC of the active Oracle Forms form and calls a public method of this PJC to pass the payload (step 2). The PJC subsequently triggers the WHEN-CUSTOM-ITEM-EVENT trigger, again passing the payload (Step 3). Finally, the PL/SQL code handling the trigger can inspect the payload and act accordingly.

Outbound JavaScript API

Next you need Oracle Forms to be able to trigger events on the Web page on which it is hosted. This can be used to pass context and/or events back from Oracle Forms to the Web application. A typical example might be scrolling through a multirecord block in Oracle Forms while passing the ID of the selected record back to the Web application. This can be used to show details of the selected object in the Web application.

This communication can be accomplished with what we call an outbound JavaScript API, an API that enables Oracle Forms to execute and evaluate JavaScript from PL/SQL.

This API works with the same PJC we already used for the inbound JavaScript API. Using the SET_CUSTOM_PROPERTY built-in from PL/SQL, you can pass parameters to the PJC (see step 1 in Figure 4):

        
begin
  -- set the Order ID
  set_custom_property('BLK_PJC.PJC', 1, 'EvalExpression',
    'document.getElementById(''frm:ordid'').value=' || :ord.ordid);
end;             

Figure 4
Figure 4 Outbound JavaScript API

In this case, the property of the PJC would be the JavaScript to be evaluated or executed. The PJC can then get a handle on its containing Java applet (see step 2 in Figure 4). This applet offers a method for evaluating the JavaScript as part of the LiveConnect API (see step 3 in Figure 4):

     
public boolean setProperty(ID property, Object value) {
  if (PROP_EVAL_EXPR == property) {
    JSObject appletWindow = JSObject.getWindow(mHandler.getApplet());
    Object evalResult = appletWindow.eval(value.toString());
    // make result available as PJC property
    setProperty(PROP_EVAL_RESULT, evalResult);
    return true;
  }
}        

This example just sets the value of a (probably hidden) input element on the Web page. You can use the same outbound JavaScript API to submit the Web page back to the Web server. This is effectively the same as a user's entering the Order ID in a search form and clicking the Submit button.

Suspend and Reuse the Applet

Oracle Forms uses a Java applet that runs in the Web browser to display the form and handle user interaction. Starting a large Java applet such as an Oracle Forms form can be resource-intensive and can take a couple of seconds. In a typical Oracle Forms installation, this does not matter, because you start the Oracle Forms applet at the beginning of your session and continue working with it for a period of time without navigating to another Web page. However, in the architecture this article introduces, a user can navigate between Web pages. Some of these might include an Oracle Forms form, and others might not. The default behavior of your Web browser and Sun Microsystems' Java Virtual Machine is to destroy the applet when you navigate away from the page.

When using Java Virtual Machine version 1.4.2 or higher on a client, you can specify an additional parameter called legacy_lifecycle in the applet's HTML. This parameter instructs the virtual machine not to destroy the applet when you navigate away from the page. Instead, the applet is kept running in the background and is put into something called the legacy lifecycle cache. The applet is reactivated when the user returns to a Web page that includes exactly the same applet declaration. This means that the applet's HTML has to be 100 percent identical for the lifecycle cache to resume an old applet. This is another reason you want to declare the HTML only once in a JSF region or JSF custom component.

Using this feature means that the applet is started only once for the entire browser session. The startup time of the applet is completely eliminated when the user returns to a page that includes an Oracle Forms form.

Act on Applet Reactivation

An applet resumes from this cache in exactly the same state it was in when it was suspended. This means that the Oracle Forms form from the previous Web page is still running. You probably want to add actions to the reactivation of the applet. Unfortunately, no Oracle Forms trigger fires during applet reactivation. The deprecated show() method of the Oracle Forms applet is called during applet reactivation. You can override this method as follows:

            
public void show() {
  super.show();
  raiseEvent("WHEN-APPLET-ACTIVATED", "");
}                       

This uses the inbound JavaScript API to send an event to Oracle Forms and can be handled in the WHEN-CUSTOM-ITEM-EVENT trigger of the PJC:

                           
declare
  BeanEventDetails  ParamList;
  ParamType         number := text_parameter;
  Event             varchar2(1000);
  Payload           varchar2(1000);
begin
  BeanEventDetails := get_parameter_list(:system.custom_item_event_parameters);
  get_parameter_attr(BeanEventDetails, 'Event',   ParamType, Event);
  get_parameter_attr(BeanEventDetails, 'Payload', ParamType, Payload);
  if event='WHEN-APPLET-ACTIVATED' then
    -- add handling here
  end if;
end;

Need for a Landing Form

You can have different Oracle ADF Faces pages in your application that all include an Oracle Forms form. These different pages probably want to call different forms. You cannot pass the names of these forms as normal parameters in the applet's HTML. Doing so would require different HTML on each Web page, which would defeat the purpose of the legacy lifecycle feature. That feature requires 100 percent identical HTML to enable reuse of the suspended applet.

You can circumvent this requirement by always specifying the same "landing form" as the name of the Oracle Forms form. The name of the actual form to be started can be included in a hidden element on the Web page:

     
<input type="hidden" id="formname" value="orders.fmx"/>  

The WHEN-NEW-FORM-INSTANCE trigger of the landing form can ascertain which form is actually needed, by evaluating document.getElementById('formname').value, using the outbound JavaScript API. The landing form can then start the requested form:

     
declare
  formName varchar2(1000);
begin
  while true loop
    -- get the form name
    set_custom_property('BLK_PJC.PJC', 1, 'EvalExpression', 
      'document.getElementById(''frm:oraFormRegion:formname'').value');
    formName := get_custom_property('BLK_PJC.PJC', 1, 'EvalResult');
    -- start the form
    call_form(formName);
    if (get_custom_property('BLK_PJC.PJC', 1, 'AppletActive')='FALSE' then
      -- exit the loop if the user is closing the browser
      exit;
    end if;
  end loop;
end;      

When the applet is reactivated from the lifecycle cache, the "real" form is running. That form should handle the applet reactivation by exiting itself. This returns control to the endless loop in the landing form, which then inspects the hosting Web page again to ascertain which form should be started. The only way this loop ends is when the applet is no longer active because the user is closing the browser.

Getting Context into Oracle Forms

The legacy lifecycle feature introduces a problem when passing context from the Web application to Oracle Forms. What if you want to incorporate an Oracle Forms form to edit an order in an Oracle ADF Faces page? You probably want to pass the selected order ID from the Oracle ADF Faces application to Oracle Forms. Again, you cannot do this the typical way by adding the parameter in the HTML to include the Oracle Forms applet. This would mean different HTML for each order and would defeat the reuse of the suspended applet.

Once more , you can work around this issue with the outbound JavaScript API. You can incorporate the order ID (or whatever context you want to pass) as an invisible item on the Oracle ADF Faces pages. The Oracle Forms form can inspect this value during the WHEN-NEW-FORM-INSTANCE trigger and act accordingly:

       
declare
  orderID  number;
begin
  -- get the order ID
  set_custom_property('BLK_PJC.PJC', 1, 'EvalExpression', 
    'document.getElementById(''frm:orderID'').value');
  customerID := get_custom_property('BLK_PJC.PJC', 1, 'EvalResult');
  :parameter.ordid:=orderID;
  -- execute query (where clause refers :parameter.ordid)
  do_key('execute_query');
end;

Visual Integration

You can opt to include a single form on an Oracle ADF Faces page and not allow users to navigate to other Oracle Forms forms. You might even go as far as making the users believe it's not even Oracle Forms they are using. For this, you might want to remove the menu, toolbar, status bar, and Oracle Forms edges.

The easiest way to achieve all this is by clipping the Oracle Forms applet. Surround the applet HTML with a single <div> element, which represents a rectangular area. The height and width of this <div> can be set smaller than the size of the Oracle Forms applet it contains. Also, the x and y position of the Oracle Forms applet can be set to negative values relative to the surrounding <div> element, which results in a small viewing window that shows only the relevant portion of the Oracle Forms form. This is demonstrated by the area boxed in red in Figure 5:

Figure 5
Figure 5 Clipped Oracle Forms form with a limited view port

Conclusion

As you can see, it is possible to extend an existing Oracle Forms application using ADF Faces. You can use ADF Faces today while protecting your Forms investment of yesterday.

You can also use the technique to switch from an Oracle Forms user experience to a more classic Web user experience. In that scenario, you would visually remove the menu, tool bar, and status bar from all Oracle Forms forms. Initially, you reuse existing Oracle Forms forms as single objects on Oracle ADF Faces Web pages. All application flow control is handled by Oracle ADF Faces, instantly giving users an intuitive Web experience while enabling you to reuse existing forms. This approach enables you to adapt an Oracle ADF Faces front end to your applications one form at a time.

The integration also enables the building of portlets that embed Oracle Forms forms. These portlets could be used in an Oracle Portal environment or with the just released Oracle WebCenter technology.

Another use of this technology is to embed individual Oracle Forms forms in Web pages to complete a BPEL human task. Oracle BPEL can monitor and control your business process. A business process might have a human task, such as a manager approving or declining a purchase order, at a certain stage. With the technology from this article, you can reuse the existing Oracle Forms form to edit a purchase order in the BPEL human task.

As you can see, the technology described here is potentially the basis for a whole array of new possibilities. If you just let the idea sink in and give it some thought, you probably can come up with other uses within your own organization. Go to www.oratransplant.nl/oracle-forms-as-web-component to get more details on this technique; you can get a step-by-step guide to building your own sample integration, read detailed papers, and get sample applications.


Wilfred van der Deijl is the senior system architect for Eurotransplant. He has more than 12 years of experience with Oracle tools. He runs an Oracle-related Weblog, OraTransplant, and is a member of Oracle's Customer Advisory Board for Development Tools.