DOWNLOAD
 Oracle JDeveloper
   TAGS
adf, java, All
product logo Taking an Oracle ADF Application from Design to Reality

By Chris Muir ACE Director and Penny Cookson ACE

Chapter 3 - The "Should-Have" Requirements: Creating an App for the General Public

Published May 2009

 Click here to see "Taking an Oracle ADF Application from Design to Reality" description and Table of Contents

In this chapter you learn how to build the SHOULD-have requirements. We will add some additional features to the application including logging of the user’s IP address if they accept our terms and conditions, and enhancing the search application's capabilities.

Our requirements can be summarized as follows:

  1. Log the user's IP address and whether they accept or reject the terms and conditions.
  2. Only enable the search fields if the terms and conditions were accepted.
  3. If more than one record is returned, hide the results.
  4. Convert search criteria to uppercase, and remove the hyphen from the waybill number.
We'll tackle these in reverse order, with the simplest requirements first.

Convert search criteria to uppercase, and remove the hyphen from the waybill number.

In databases data is commonly stored in a format different from how the users enter it. Often peoples' names are stored in uppercase in the database, yet we allow the user to search by mixed-case names.

Oracle ADF Business Components offers a number of mechanisms to intercept the search criteria, or what we call bind variables in Oracle ADF Business Components, before they hit the database. The easiest mechanism is to modify the View Objects query to do the conversions for you. For example, consider the modified query from our original View Object as follows:

Figure1

As usual when modifying our Oracle ADF Business Components it's best to test changes in the Business Component Browser. From our testing we reveal that we can now enter lowercase data, as well as waybill numbers with hyphens, and the application still returns a result.

There is another mechanism to modify the bind variables, namely the bindParametersForCollection() method in the underlying ViewObjectImpl, which acts as a chokepoint method for viewing and modifying View Object bind variables before they're applied against the View Object. Because we have a simple solution above, we'll stick with it. We'll keep the bindParametersForCollection() solution keep up our sleeve for later, because it may prove useful when we need to log the search criteria to the database.

If more than one record is returned, hide the results.

This requirement is an odd one; one would expect we'd want to show all of the results from our View Object in the page. In this case the underlying table has no primary or unique keys allowing duplicate records based on our search criteria. If more than one record is displayed, there's a privacy requirement that a customer shouldn't be able to see another customer's data. As Oracle ADF programmers, we don't always have the privileges or controls to fix the underlying data problems. We just need to work with what we've got.

In looking at the underlying data, it seems the requirements haven't been thought through:

Lodged With

First Name

Last Name

Waybill #

Sender

Location

Status

Update

WLYP

CHRIS

MUIR

ABC1234

M.ALAN

PERTH

TRANSIT

23/02/2009

ACME

JOE

DOE

DDD4433

A.SMITH

SYDNEY

DELIVERED

21/02/2009

ACME

JOE

DOE

DDD4433

A.SMITH

SYDNEY

TRANSIT

20/02/2009

WLYP

CHRIS

MUIR

ABC1234

K.BAKER

PERTH

DELIVERED

07/01/2009

Given our search fields, if we consider ACME-JOE-DOE-DDD4433, our system would in fact return two records (highlighted in red above)—one showing the parcel in transit, the later one delivered. In this case our security constraint—the user shouldn't see a result if more than one record is returned—is going to cause problems. We've revealed a hidden requirement that for the same Lodged With, First Name, Last Name, Waybill # and Sender search we should return the latest record.

So how do we solve this problem and ensure that only the most recent status record for a particular parcel is shown? Again, thanks to the power of SQL queries, we can modify our View Object to return only the latest record for each parcel using a subquery as follows:

Figure2

Again, testing in our Business Component Browser, we see that a query on ACME-JOE-DOE-DDD4433 returns only one record, but a query on WLYP-CHRIS-MUIR-ABC1234 returns two, exactly what we wanted.

In the case of a search on WLYP-CHRIS-MUIR-ABC1234 that returns two records, we can't do anything about tidying up this data, so in this case on our Web page we'll just not show the results, and ask the customer to call us instead.

For our SearchPage.jspx, as a result of dragging in the View Object from the Component Palette onto our page, behind the scenes Oracle JDeveloper has created a page definitions file for us with a number of bindings:

Figure3

One of the bindings is the iterator ParcelsView1Iterator, which has an attribute estimateRowCount that we can access in our Web page via a JavaServer Faces Expression Language (EL) expression. This attribute returns the number of records returned to the iterator and is ideal in our case, because we want to know the number of records returned. For each of the result fields on our page, we can set their rendered property as follows:

Figure3

For debugging purposes, just before the result fields we'll put an outputText that literally outputs the number of records returned:

<af:outputText
    value="Numbers of rows: #{bindings.ParcelsView1Iterator.estimatedRowCount}"/>
Note that on running our Web page now, the result fields don't initially show:

Figure4

Once we enter the search criteria WLYP-CHRIS-MUIR-ABC1234 we should see that the outputText says two records have been returned, and the result fields remain hidden:

Figure5

However, if we enter the search criteria ACME-JOE-DOE-DDD4433 we get:

Figure6

Only enable the search fields if the terms and conditions were accepted.

Looking back at our original storyboard of the enquiry screen, we can see that we want to show terms and conditions that the user must agree to, by selecting either the Accept or Reject buttons. On entry to the screen the Enquiry Criteria fields and the Enquiry button are disabled until the user presses the Accept button. If the user presses the Reject button, the fields remain read-only. And once the user presses either button, the buttons themselves are disabled such that the user can't change their choice unless they start a new session.

Note the emphasis on session in the previous paragraph: For a single session we'll enforce the rule the user accepts or rejects the terms and conditions for that entire session. Once accepted, the terms and conditions are accepted for the entire session until the user leaves. However, upon return, the user will be prompted to accept or reject the terms and conditions again. This is a typical requirement across many contemporary sites.

Log the user's IP address and whether they accept or reject the terms and conditions.

In addition to the users accepting or rejecting the terms and conditions, it's necessary to record the user's IP address and which button they selected. This is for auditing purposes later on: if a customer complains about a privacy breach, we can track backward and say that a particular IP address accepted or rejected the terms and conditions which included our privacy disclaimers.

Ah, we nearly missed a requirement. We also need to record whether the user is a sender or recipient of a parcel, along with the IP address and if they accepted or rejected the terms and conditions. To do this we want to display a poplist before the terms and conditions text, which explicitly forces the user to select and specify the type of customer they are: namely, the person sending or receiving the package.

To achieve the above requirement we must implement the following:
  • Annotate the screen with our terms and conditions and add the Accept and Reject buttons.
  • Create a session level bean to which the buttons map.
  • Enable the search fields and Enquiry button if the Accept button is pressed.
  • Add the sender/receiver poplist.
  • Add code in the bean to capture the user's IP address.
  • Write the user's IP address and choice to the database.

Annotate the screen with our terms and conditions and add the Accept and Reject buttons.

This is the simplest of our requirements and has the following results:

Figure7

Ideally the terms and conditions would be sourced from the database and via a pop-up menu we'd show the complete legal jargon. But we'll keep it simple until our lawyers can work out what they really want to include.

Here's the code for the new additions:

<af:outputText value="Do you accept the terms and conditions?"/>
<af:panelGroupLayout layout="horizontal">
    <af:commandButton text="Accept"/>
    <af:spacer height="5" width="5"/>
    <af:commandButton text="Reject"/>
</af:panelGroupLayout>

Create a session-level bean to which the buttons map.

Next upon the users pressing either the Accept or Reject button, we need to store the result in a session bean. Within our ViewController project, we'll create a Java class view.beans.EnquiryBean as follows:

package view.beans;

import javax.faces.event.ActionEvent;

public class EnquiryBean {

  String termsAndConditions = "unset";

  public void setTermsAndConditions(String termsAndConditions) {
    this.termsAndConditions = termsAndConditions;
  }

  public String getTermsAndConditions() {
    return termsAndConditions;
  }

  // Intended for the "Accept" button for the terms and conditions
  public void acceptTermsAndConditions(ActionEvent event) {
    setTermsAndConditions("accepted");
  }

  // Intended for the "Reject" button for the terms and conditions
  public void rejectTermsAndConditions(ActionEvent event) {
    setTermsAndConditions("rejected");
  }
}
Note that we have an instance attribute termsAndConditions so that when the class is instantiated, the attribute's value is "unset" implying that the user has yet to accept or reject the terms and conditions. Underneath this the next two methods provide the getter and setter accessors for reading and writing this attribute.

In addition we've provided two ActionEvent methods that we'll map to our respective command buttons' actionListener properties. The acceptTermsAndConditions button sets the EnquiryBean termsAndConditions attribute to "accepted", and the rejectTermsAndConditions button sets the termsAndConditions attribute to "rejected".

Before we can modify our commandButtons to call these two methods, we need to configure the EnquiryBean as a session bean in our Oracle ADF Controller. We do this by opening the ViewController adfc-config.xml file in the Application Navigator:

Figure8

We enter the EnquiryBean details under the Overview tab Managed Beans section as follows:

Figure9

Once we've configured the bean we remap our commandButtons to call the ActionEvent methods in the EnquiryBean via the actionListener property:
<af:commandButton text="Accept"
                   
                              
actionListener="#{enquiryBean.acceptTermsAndCondition}"/>
<af:spacer height="5" width="5"/>
<af:commandButton text="Reject"
                   
                              
actionListener="#{enquiryBean.rejectTermsAndConditions}"/>
                            

Enable the search fields and Enquiry button if the Accept button is pressed.

To disable both the Accept and Reject buttons once either button is pressed, we again modify the commandButtons including a disabled EL expression, as follows:

<af:commandButton text="Accept"
                  actionListener="#{enquiryBean.acceptTermsAndConditions}"
                   
                              
disabled="#{enquiryBean.termsAndConditions != 'unset'}"/>
<af:spacer height="5" width="5"/>
<af:commandButton text="Reject"
                  actionListener="#{enquiryBean.rejectTermsAndConditions}"
                   
                              
disabled="#{enquiryBean.termsAndConditions != 'unset'}"/>
                            
And to disable the enquiry fields we add the following disabled EL expression to each field and to the Enquiry button:
<af:inputText value="#{bindings.pLodgedWith.inputValue}"    
              label="#{bindings.pLodgedWith.hints.label}"
              required="#{bindings.pLodgedWith.hints.mandatory}"
              columns="#{bindings.pLodgedWith.hints.displayWidth}"
              maximumLength="#{bindings.pLodgedWith.hints.precision}"
              shortDesc="#{bindings.pLodgedWith.hints.tooltip}"
              rendered="true"
               
                              
disabled="#{enquiryBean.termsAndConditions != 'accepted'}">
                            
Now on entering the Web page we're presented with both the Accept and Reject buttons enabled, but the enquiry fields and Enquiry button disabled:

Figure10

If we press the Accept button, both the Accept and Reject buttons are disabled, and the enquiry fields and Enquiry button are enabled:

Figure11

If we start a new browser session and instead accept the Reject button, both the Accept and Reject buttons are disabled, and the enquiry fields and the Enquiry button remain disabled:

Figure12

Add the sender/receiver poplist.

Our storyboard shows that we must also record if the user is the person who sent the parcel or the person who will receive the parcel. This will give us valuable information on who is using our system.

Now the values "sender" and "recipient" could be hard-coded into an Oracle ADF Faces RC poplist binding, but we know from experience that our boss tends to expand requirements later on. For instance, a value of "call center" is a dead-certain to be added once our boss realizes our call center will use this application too. Ideally, rather than hard-coding these values, we'd like to source them from a database table. We're not allowed to touch or change the database at this stage, but with Oracle JDeveloper 11g we can setup the Oracle ADF Business Components structures to accept the data as if it were coming from the database, with a static View Object.

Doc Tip: Don't know how to create an a View Object with bind variables? See Chapter 5 Section 5.3, "Populating View Object Rows with Static Data", in Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework 11g Release 1 for instructions.

Under our Model project, we create a new View Object EnquirySourceView by selecting the "Rows populated at design time (Static List)" option.

Figure13

We create two transient attributes Code and Description:

Figure14

And we prepopulate the static list with two rows, as follows:

Figure15

Later on if we need to add more rows, we can. Alternatively if we finally have the ability to create a database table to store these values, we can delete the Static List View Object, and replace it with a Read/Only View Object, without affecting our SearchPage's poplist.

Once we have this View Object, we expose it through our Application Module and it's now available to our ViewController's SearchPage to use.

Figure16

To add a Select One Choice to our page in this example, we can't simply drag and drop the EnquirySourceView1 from the Data Control Palette. A typical data-bound control mapped to Oracle ADF Business Components wants to read and write its values back to the model layer. In our case, we only want to read the values, and grab the selected value and place in an instance variable in our Java session bean.

To do this, we'll manually create our own list binding. First we switch to the SearchPage's binding page and press the plus sign button under the Bindings section:

Figure17

From the Insert Item dialog box we pick the list binding:

Figure18

In the case of the Select List Binding Type dialog box, the fourth option matches our requirements: we want to read the values from a base datasource into a standalone select one choice, but we don't want the choice to update the base data source from which the values were read:

Figure19

In the Create List Binding dialog box we then need to create a base datasource to map against the EnquirySourceView object. In essence this creates an iterator in the bindings for the SearchPage to draw its data from the EnquirySourceView. We do this by selecting the Add button:

Figure20

and then in the Add Data Source dialog box selecting our EnquirySourceView1 datasource.....

Figure21

This datasource View Object creates the named iterator at the bottom of the dialog box, and on pressing the OK button and returning to the Create List Binding dialog box places the selected View Object in the Base Data Source drop-down poplist as follows:

Figure22

The remaining options allow us to choose which attribute (Code or Description) to show to the user from the EnquirySourceView. We'd rather show the more meaningful Description; if a blank labeled item should be shown, we want to show a blank option at the beginning of the list to force the user to select a value rather than accepting a default value. We'll omit the Most Recently Used facility because we have only a few values to show in the list.

The resulting list entry in the binding page will be called "Description" which isn't ideal. We'll select the Description binding and via the Property Inspector rename it to "EnquirySourceList":

Figure23

If we open the XML source code for the SearchPage's page definition file (in the above example view/pageDefs/SearchPagePageDef.xml) we should see the following entry for the EnquirySourceList binding:
<list IterBinding="EnquirySourceView1Iterator"
      DTSupportsMRU="true"
      StaticList="false"
      ListIter="EnquirySourceView1Iterator"
      id="EnquirySourceList"
      NullValueFlag="start"
      ListOperMode="navigation">
  <AttrNames>
    <Item Value="Description"/>
  </AttrNames>
</list>
(Note: Oracle JDeveloper 11g build 5188 has a few bugs on creating the list binding so your XML value above may differ. It's appropriate to fix the entries in this case).

Once we've created the binding, in our Web page we then drag a Select One Choice from the Component Palette which presents the Insert Select One Choice dialog box as shown below. We want to retrieve the values for the Select One Choice from the EnquirySourceList binding, so we use the following EL expression:

Figure24

In the Common Properties set the label to "Who are you?" This finally results in the following code:
<af:selectOneChoice label="Who are you?">
  <f:selectItems value="#{bindings.EnquirySourceList.items}"/>
</af:selectOneChoice>
Running our page, we see the following:

Figure25

Our Select One Choice is now correctly sourcing its values from the underlying View Object. However, upon the user selecting an option we need to write the value to somewhere where we could retrieve it. Typically we'd write the value back to our Oracle ADF Business Component model layer, but in this case we don't want to persist the choice in an Entity Object or View Object, but rather temporarily store the value so we can grab its value in the EnquiryBean.

The best place to store the value is actually in the EnquiryBean. We'll add the following instance variable and accessors:

String enquirySourceIndex;

public void setEnquirySourceIndex(String enquirySourceIndex) {
  this.enquirySourceIndex = enquirySourceIndex;
}

public String getEnquirySourceIndex() {
  return enquirySourceIndex;
}
which we can then make use of in our selectOneChoice value attribute:
<af:selectOneChoice
   label="Who are you?"
   value="#{enquiryBean.enquirySourceIndex}"
   required="true"
   disabled="#{enquiryBean.termsAndConditions != 'unset'}">
   <f:selectItems value="#{bindings.EnquirySourceList.items}"/>
</af:selectOneChoice>
Note that we've also added a Disabled attribute to set the field to read only-once the user accepts the terms and conditions. In addition we've made the field mandatory through the required property.

Next for the session, along with the user's IP address and acceptance or rejection of the terms and conditions, we need to store the Select One Choice value too.

To do this is relatively easy, thanks to the bindings we just created. In our EnquiryBean, we'll add the following method to retrieve the Select One Choice value via the bindings as following:

private String getEnquirySourceCode() {
  DCBindingContainer dcBinding = 
 (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
  
  DCControlBinding listControlBinding = 
  
                              
dcBinding.findCtrlBinding("EnquirySourceList");
  FacesCtrlListBinding listBinding = (FacesCtrlListBinding)listControlBinding;
  Row row = 
 listBinding.getRowAtRangeIndex(Integer.valueOf(enquirySourceIndex));
  String enquiryListCode = row.getAttribute("Code").toString();

  return enquiryListCode;
}
                            
Note how in the bolded line we first retrieve the EnquirySourceList binding, which is the attribute value binding that maps to the dummy iterator variable we created to store the selected Select One Choice value. By default the Select One Choice stores the index value of the option the user chooses in the poplist. Therefore we then extract the EnquirySourceList binding and ask it to retrieve the row at the index value, and, then retrieve the Code attribute value for that retrieved row.

Add code in the bean to capture the user's IP address.

At this stage, we've added the Accept and Reject buttons on the screen, as well as the sender/receiver poplist. In addition, we have the session bean behind the scenes that has method's in it to handle which button the user presses, as well as the last function getEnquirySourceCode() to retrieve the sender/receiver poplist's value.

Within the acceptTermsAndConditions and rejectTermsAndConditions methods, we want to gather details about the user, namely the user's IP address. This can be easily done via the following code in our EnquiryBean session bean:

public String getRemoteAddr() {
  String remoteAddr =
      ((HttpServletRequest)FacesContext.getCurrentInstance().    (cont next line)
           getExternalContext().getRequest()).getRemoteAddr();
  return remoteAddr;
}

Write the user's IP address and choice to the database.

This last task actually links us back to our first "should have" requirement, namely: Log the user's IP address plus whether they accept or reject the terms and conditions(as well as if the user is a sender or a receiver). At this stage we want to implement the current requirement; its solution will be coupled with the next requirement.

Like all other problems above, the easiest way to solve this one is to break into solvable parts. This particular problem breaks into the following parts:

  • Find a suitable event when to record this information. In recording details about the user, we need to think about why we're recording the information. In our case, it is to log whether the user accepted or rejected the terms and conditions. The best time to record the user information is when the user actually presses the Accept or Reject button.

  • Find a suitable code chokepoint to gather the data we need. Given that we now know we want to record the user information when the user presses the Accept or Reject button, the obvious code chokepoint in which to place our further logic in is the acceptTermsAndConditions and rejectTermsAndConditions methods we added to our EnquiryBean session bean. At this point, our bean looks as follows:
    public class EnquiryBean {
    
      String enquirySourceIndex;
      String termsAndConditions = "unset";
    
      public void setEnquirySourceIndex(String enquirySourceIndex) {
        this.enquirySourceIndex = enquirySourceIndex;
      }
    
      public String getEnquirySourceIndex() {
        return enquirySourceIndex;
      }
    
      public void setTermsAndConditions(String termsAndConditions) {
        this.termsAndConditions = termsAndConditions;
      }
    
      public String getTermsAndConditions() {
        return termsAndConditions;
      }
    
      // Intended for the "Accept" button for the terms and conditions
      public void acceptTermsAndConditions(ActionEvent event) {
        setTermsAndConditions("accepted");
      }
    
      // Intended for the "Reject" button for the terms and conditions
      public void rejectTermsAndConditions(ActionEvent event) {
        setTermsAndConditions("rejected");
      }
    
      public String getRemoteAddr() {
        String remoteAddr =
          ((HttpServletRequest)FacesContext.getCurrentInstance(). (cont next line)
                         getExternalContext().getRequest()).getRemoteAddr();
        return remoteAddr;
      }
    
      private String getEnquirySourceCode() {
        DCBindingContainer dcBinding = (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
        
        DCControlBinding listControlBinding = dcBinding.findCtrlBinding("EnquirySourceList");
        FacesCtrlListBinding listBinding = (FacesCtrlListBinding)listControlBinding;
        Row row = listBinding.getRowAtRangeIndex(Integer.valueOf(enquirySourceIndex));
        Object[] enquiryListValues = row.getAttributeValues();
        String enquiryListCode = row.getAttribute("Code").toString();
    
        return enquiryListCode;
      }
    }
    
  • Gather the data we wish to record, including the user's IP address, if they accept or reject the terms and conditions, and if the user is a sender or a receiver. Given that we now know the methods to gather the data, we can augment the acceptTermsAndConditions() and rejectTermsAndConditions() methods to easily retrieve the user IP address, the button selection, and the poplist selection, thanks to the getRemoteAddr(), getTermsAndConditions() and getEnquirySourceCode() methods we created earlier.

    We could create a new method storeUserInformation() that is called from both acceptTermsAndConditions() and rejectTermsAndConditions() as follows:
    // Intended for the "Accept" button for the terms and conditions
    public void acceptTermsAndConditions(ActionEvent event) {
      setTermsAndConditions("accepted");
      storeUserInformation();
    }
    
    // Intended for the "Reject" button for the terms and conditions
    public void rejectTermsAndConditions(ActionEvent event) {
      setTermsAndConditions("rejected");
      storeUserInformation();
    }
    
    public void storeUserInformation() {
      String userIPAddr = getRemoteAddr();
      String userTermsAndConditions = getTermsAndConditions();
      String userEnquirySource = getEnquirySourceCode();
    }
    
    As you can see, the storeUserInformation() method retrieves all the values that we're interested in persisting to the database. At this point it would be a really good idea to throw a breakpoint into this method and run the application under a debug session, to see what values are retrieved by the storeUserInformation() method. Before we write the data to the database, we should make sure that we actually have data to write and that we haven't made a mistake!

    If all goes well this leads us onto our final task:

  • Write the user information to the database. At this point, we have three pieces of information that we want to record about the user: Their IP address, if they accepted or rejected the terms and conditions, and if they are a sender or receiver of a parcel.

    We again need to consider how we're going to accomplish this task. In order to write this data to the database we first need a table, so we'll beg our DBA to create the following:

    CREATE TABLE log_session 
    (log_session_id   NUMBER(10)   NOT NULL
    ,user_ip          VARCHAR2(15) NOT NULL 
    ,terms_conditions VARCHAR2(8)  NOT NULL
    ,enquiry_source   VARCHAR2(4)  NOT NULL
    ,datetime         DATE         NOT NULL 
    ,CONSTRAINT log_session_pkg PRIMARY KEY (log_session_id));
    
    We'll also ask our DBA to create the following sequence to populate the log_session_id:
    CREATE SEQUENCE log_session_seq;
    
    We then need to choose how we'll write the data to the table. From our session bean, we could just write raw JDBC to write the log entries. However this isn't ideal because we have the entire Oracle ADF Business Components model layer that is ideally suited to writing to the database and doesn't require us to write a single line of JDBC code which can be error-prone.

    We could create an Oracle ADF Business Components Entity Object and View Object based on the log_session table to write values to the database, utilizing our existing Oracle ADF Business Component Application Module. However, logging on to the database has one very special characteristic that makes reusing our existing Application Module that we created in our application inherently a problem.

    As you are aware, the Application Module within Oracle ADF Business Components, or more precisely the root Application Module, is responsible for transactional control with the database; the Application Module issues the commit and rollbacks that ultimately save the underlying View Object and Entity Object data to the database. So for a standard Oracle ADF Business Components CReated-Update-Delete (CRUD) like application our root Application Module will take care of allowing the user to make multiple changes to the underlying data and then saving the results.

    Yet in the case of logging, regardless of what the user is doing, we must save the results to the database as soon as a log entry is made. The log event requires its own transaction separate to that of the CRUD application's Application Module transaction, which our SearchPage is based on. The PL/SQL analogy of such a feature is known as an "autonomous transaction".

    One approach to solve this is to create a separate logging root Application Module that controls its own transactions, with its own View Object and Entity Objects that we can log through. However, there is an easier way. Utilizing the ability of the Oracle ADF Business Components framework, you can make JDBC calls to the database utilizing the current root Application Module's connection but wrapping the JDBC anonymous PL/SQL block in an autonomous_transaction pragma.

    To do this, we'll implement a method called logSession() in our AppModuleImpl:

    public void logSession(String userIp,
                           String termsConditions,
                           String enquirySource) {
    
      DBTransaction trans = getDBTransaction();
    
       
                                      
    SequenceImpl seq = new SequenceImpl("LOG_SESSION_SEQ", trans);
      setLogSessionId(seq.getSequenceNumber());
    
      CallableStatement statement = null;
    
      String plsql =
        "DECLARE "
       +  "PRAGMA AUTONOMOUS_TRANSACTION;"
       + "BEGIN "
       +  "INSERT INTO log_session "
       +  "(log_session_id, user_ip, terms_conditions, enquiry_source, datetime) "
       +  "VALUES "
       +  "(?,?,?,?,sysdate);"
       +  "COMMIT;"
       + "END;";
    
      statement = trans.createCallableStatement(plsql, 4);
      try {
        statement.setInt(1, getLogSessionId().intValue());
        statement.setString(2, userIp);
        statement.setString(3, termsConditions);
        statement.setString(4, enquirySource);
    
        int rows = statement.executeUpdate();
    
      } catch (SQLException s) {
        throw new JboException(s);
      } finally {
        try {
          if (statement != null)
            statement.close();
        } catch (SQLException s) { /* ignore */ }
      }
    }
                                    
    Note how we retrieve the log_session_seq and store it in a variable of the AppModuleImpl via a call to setLogSessionId. The retrieved LogSessionId sequence number will become useful later. In order to store the LogSessionId in the AppModuleImpl, we've also defined the following instance variable and accessor methods:
    private Number logSessionId;
    
    public void setLogSessionId(Number logSessionId) {
      this.logSessionId = logSessionId;
    }
    
    public Number getLogSessionId() {
      return logSessionId;
    }
    
    In order to store instance variables within the Application Module, that will be persisted over many user requests, we need to override the activateState and passivateState() methods in the AppModuleImpl as follows:
    private static final String LOG_SESSION_ID = "jbo.logSessionId";
    
    @Override
    protected void passivateState(Document document, Element element) {
      Number logSessionIdNum = getLogSessionId();
      if (logSessionIdNum != null) {
        int logSessionIdInt = logSessionIdNum.intValue();
        Node node = document.createElement(LOG_SESSION_ID);
        Node cNode = document.createTextNode(Integer.toString(logSessionIdInt));
        node.appendChild(cNode);
        element.appendChild(node);
      }
    }
    
    @Override
    protected void activateState(Element element) {
      super.activateState(element);
      if (element != null) {
        NodeList nodeList = element.getElementsByTagName(LOG_SESSION_ID);
        if (nodeList != null) {
          for (int i = 0, length = nodeList.getLength(); i < length; i++) {
            Node child = nodeList.item(i).getFirstChild();
            if (child != null) {
              try {
                setLogSessionId(new Number(child.getNodeValue()));
                break;
              } catch (SQLException ex) {
                new JboException(ex.getMessage());
              }
            }
          }
        }
      }
    }
    
    We then modify our EnquiryBean's storeUserInformation() method to call the Application Module's logSession() method
    public void storeUserInformation() {
      String userIPAddr = getRemoteAddr();
      String userTermsAndConditions = getTermsAndConditions();
      String userEnquirySource = getEnquirySourceCode();
      
      DCDataControl dc = BindingContext.getCurrent().getDefaultDataControl();
      ApplicationModule am = (ApplicationModule)dc.getDataProvider();
      AppModuleImpl service = (AppModuleImpl)am;
      
      service.logSession(userIPAddr, userTermsAndConditions, userEnquirySource);
    }
    
    Note that in the Oracle JDeveloper source editor it may be necessary to recompile (rebuild) the Model project and then the ViewController project, to get Oracle JDeveloper to recognize the AppModuleImpl's new logSession() method from the Model project. Otherwise, you may see a syntax error with no chance of importing the class to use. Once you have rebuilt both projects, then import model.AppModuleImpl and the syntax error will disappear.

    If we run our SearchPage, and either accept or reject the terms and conditions the user information is written to the log table.

    Figure26

Summary

In this chapter we have added code to satisfy the "Should-Have" requirements of our application, including logging of the user's IP address if they accept our terms and conditions, and enhancing the search capabilities. In the next chapter we will add functionality to log all search criteria entered by the user, as well as the number of records returned.  Click here to see "Taking an Oracle ADF Application from Design to Reality" description and Table of Contents