A tale of two applications: part 1
Build your first application—an application that
doesn't use Ajax with XQuery.
The business requirements
Congratulations! You
have just been promoted to chief information officer (CIO) of
FishinHole.com, an e-commerce corporation that markets fishing tackle over
the Internet. Your marketing department has just informed you that
there is a desperate need for a search facility on the Web site. This
facility must let users search for lures by usage (casting or
trolling) and configuration (minnow shaped or spoon shaped). This is a
great chance for you to make a good first impression.
Before you got
promoted, your predecessor kept the entire FishinHole.com catalog in XML
format. So, you won't need to worry about querying against a relational
database to fulfill these requirements. However, you will need to query the
XML document as shown in Listing 1.
Listing 1. fishinhole.xml
<lures>
<casting>
<minnows brand="Yohuri" style="deep" size="3">
<minnow color="midnight blue">
<international-prices>
<price denomination="dollar">3.25</price>
<price denomination="euro">4.25</price>
<price denomination="canadian-dollar">4</price>
<price denomination="peso">100</price>
</international-prices>
<availability>
<shipping>
<delay>0</delay>
</shipping>
<regions>
<shipping-region>United States</shipping-region>
<shipping-region>European Union</shipping-region>
<shipping-region>Canada</shipping-region>
<shipping-region>Mexico</shipping-region>
</regions>
</availability>
</minnow>
...
|
The
existing FishinHole.com Web site is already implemented as a Java
enterprise application running with the Spring Framework on a Tomcat
server. Those technologies will be left in place.
Coding it out
The first thing you
need to do is to put the framework in place, then you can build the actual
implementation of this business requirement on top of that. By way of
analogy, you first create the skeleton, then you put meat on the bones. So,
you configure your web.xml file as shown in Listing 2.
Listing 2. web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>FishinHole</display-name>
<servlet>
<servlet-name>FishinHole</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>FishinHole</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
|
Of
particular importance in this listing is that the Web application is
configured to use the DispatcherServlet that the
Spring Framework provides. URLs that use the FishinHole context and end with the .htm extension will be
intercepted by that servlet.
You are following conventional patterns
for implementing the solution. One pattern is the familiar MVC pattern,
which separates information representation (the model) from presentation
(the view) from response to user input (the controller). This will be
accomplished using the Spring Framework.
Another pattern you will
implement is a strong separation of concerns between your controller and
your data-access object. The old-schoolers may refer to this as the
Business Delegate pattern. Sometimes, it is just called a
service or a manager.
The first thing you implement is the data-access object, shown in Listing 3. This is the code that accepts criteria and
queries the XML document for elements that match the criteria. You decide
on a simple StAX parser for this purpose.
Listing 3. LuresDao
public class LuresDao {
private static final String XML_FILE = "c:/fishinhole/fishinhole.xml";
public List<Lure> fetchLuresByUsageAndConfiguration(String usage,String configuration) {
List<Lure> lures = new ArrayList<Lure>();
try {
XMLInputFactory inputFactory = XMLInputFactory.newInstance();
InputStream in = new FileInputStream(XML_FILE);
XMLEventReader eventReader = inputFactory.createXMLEventReader(in);
boolean usageMatch = false;
String currentBrand = null;
Integer currentSize = null;
while (eventReader.hasNext()) {
XMLEvent event = eventReader.nextEvent();
if (event.isStartElement()) {
if (event
.asStartElement()
.getName()
.getLocalPart()
.equals(usage)) {
usageMatch = true;
}
if (event
.asStartElement()
.getName()
.getLocalPart()
.equals("minnows")
&& configuration.equals("minnow") && usageMatch) {
QName qName = QName.valueOf("brand");
currentBrand = event
.asStartElement()
.getAttributeByName(qName)
.getValue();
qName = QName.valueOf("size");
currentSize = new Integer(event
.asStartElement()
.getAttributeByName(qName)
.getValue());
}
if (event
.asStartElement()
.getName()
.getLocalPart()
.equals("spoons")
&& configuration.equals("spoon") && usageMatch) {
QName qName = QName.valueOf("brand");
currentBrand = event
.asStartElement()
.getAttributeByName(qName)
.getValue();
qName = QName.valueOf("size");
currentSize = new Integer(event
.asStartElement()
.getAttributeByName(qName)
.getValue());
}
if (event
.asStartElement()
.getName()
.getLocalPart()
.equals(configuration) && usageMatch) {
QName qName = QName.valueOf("color");
String color = event
.asStartElement()
.getAttributeByName(qName)
.getValue();
Lure lure = new Lure();
lure.setConfiguration(configuration);
lure.setColor(color);
lure.setBrand(currentBrand);
lure.setUsage(usage);
lure.setSize(currentSize);
lures.add(lure);
continue;
}
} else if (event.isEndElement()) {
if (event.asEndElement().getName().getLocalPart().equals(usage)) {
usageMatch = false;
}
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (XMLStreamException e) {
e.printStackTrace();
}
return lures;
}
}
|
It
is beyond the scope of this tutorial to explain the use of StAX. The basic
idea behind this code is that the method fetchLuresByUsageAndConfiguration accepts two parameters: usage and configuration.
Usage refers to how the lure is actually used (casting or
trolling). Configuration refers to the physical shape of the lure
(spoon or minnow). The method searches the XML document found at
C:\fishinhole\fishinhole.xml for elements that match that criterion. It
then translates those elements into a list of lure objects, simple beans with properties that
identify important information about the lures.
The service component simply implements one method
(also called fetchLuresByUsageAndConfiguration)
and delegates that method to LuresDao.
The controller defines what happens when
a user accesses the FishinHole Web context and
the catalog.htm page, shown in Listing 4.
Listing 4. CatalogController
protected ModelAndView handleRequestInternal(HttpServletRequest request,
HttpServletResponse response) throws Exception {
ModelAndView mv = new ModelAndView();
mv.setViewName("catalog");
String usage = request.getParameter("usage");
String configuration = request.getParameter("configuration");
System.out.println("usage is " + usage);
System.out.println("configuration is " + configuration);
if (usage != null
&& !usage.equals("")
&& !usage.equals("0")) {
if (configuration != null
&& !configuration.equals("")
&& !configuration.equals("0")) {
List<Lure> lures = luresService
.fetchLuresByUsageAndConfiguration(usage, configuration);
mv.addObject("lures", lures);
System.out.println("size is " + lures.size());
}
}
return mv;
}
|
The
idea here is that if catalog.htm looks at the URL string for two
parameters—usage and configuration—and both of those
parameters exist and have valid values, the controller invokes fetchLuresByUsageAndConfiguration on the service.
The service, as you will recall, returns a list of lure objects that match those criteria. This list is then added
to the model, where the JavaServer Pages (JSP) page can
display it.
Deployment
If you haven't done so already, this would be an
excellent time to go the Downloads section and
grab the first Web archive (WAR), FishinHole.war. For licensing reasons,
the Java archives (JARs) needed to actually run the
WAR—shown below—on the Tomcat server were not
included:
- ddxq.jar
- ddxqsaxon8.jar
- ddxqsaxon8a.jar
- commons-logging-1.1.1.jar
- jstl.jar
- log4j-1.2.9.jar
- spring-web.jar
- spring-webmvc.jar
- spring.jar
- standard.jar
The good news is that all the JAR files except for the first three can be
found within the Spring distribution. The others are part of XQJ. See Resources for information on
downloading these technologies.
When you have obtained the necessary
JAR files, simply include them in the WEB-INF/lib directory of the WAR file. This is
as simple as treating the WAR file as a standard compressed file (for example, a
file in ZIP format) and adding those files to that location.
When
the WAR file is complete, you need to put the data and configuration files
in place. For purposes of this tutorial, it's assumed that these files will
reside in the C:\fishinhole directory. In that directory, you should have the following
files:
- fishinhole.xml
- searchResults.xq
The second file will not be used in this part of the tutorial, but go
ahead and put it there anyway as a forward-looking measure.
If you
need to change the location of the files for any reason, please make sure
that you update the appropriate line in Listing 3.
Now, launch Tomcat and deploy the WAR file. Doing so requires a basic
understanding of Tomcat server administration, which is beyond the scope of
this tutorial. It is assumed that you are running the server on localhost
(127.0.0.1) and using port 8080 to listen for requests. The latter is the
Tomcat default.
The fruits of
your labor
Open a browser, and point your URL to
http://localhost:8080/FishinHole/catalog.htm. You should see something like
Figure 1.
Figure 1. The Catalog page
Now, from the Usage drop-down list, select Trolling; from the
Configuration drop-down list, select Minnow. Click
Search. You should now see something like Figure
2.
Figure 2. The search
results
A couple of things should be noted at this point. First, did you notice how
the entire page reloaded when you clicked Search? It was quick, but
it happened. If you didn't notice, feel free to perform another search.
Select a different combination, then click Search again. The entire
page reloads.
Another thing to note is that the default options are
selected from the drop-down lists. In this case, the --SELECT--
options are preselected as opposed to the actual options that you selected.
Yes, this is fixable with some JavaServer Pages Standard Tag Library (JSTL)
work and server-side settings, but the point is that when the entire page
is reloaded, you have to account for setting those values. If, however,
only a small subset of the page is reloaded, independent of the drop-down
lists, then you don't have to worry about tracking and resetting the
appropriate values.
An unexpected response
You show these results to your
marketing department personnel and quickly learn that this solution is not
acceptable. They inform you that entire page reloads and the default values
being displayed in the drop-down lists after the search results are
unacceptable to your customer base. They begin to question your ability as
a CIO. You go home depressed.
That night you have a dream about
Ajax.
|