Articles
Application Development Framework
Chapter 3 - The "Should-Have" Requirements: Creating an App for the General PublicPublished 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:
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: 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:
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: 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: 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:
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.This is the simplest of our requirements and has the following results: 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:
<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:
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.
Under our Model project, we create a new View Object EnquirySourceView by selecting the "Rows populated at design time (Static List)" option. 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. 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: 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":
<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:
<af:selectOneChoice label="Who are you?">
<f:selectItems value="#{bindings.EnquirySourceList.items}"/>
</af:selectOneChoice>
Running our page, we see the following:
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:
SummaryIn 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.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||