24.1. What Is PDE Build?At its heart, PDE Build is an Ant script generator. It takes in a collection of plug-ins and features, their manifests and build.properties files, and generates a set of Ant build scripts. These scripts are run to produce a build. The export operations you have been doing throughout this book use PDE Build under the covers. PDE Build is quite flexible. It can consume hybrid mixes of plug-ins and features that are pre-built and those that remain to be built. Some may be included in the final output, while others may not. The output of a build can also vary from plug-ins in directories to update sites and Zip archives of JAR'd and signed plug-ins and features. The build mechanism builds plug-ins and features, or cascades of the transitively included features and plug-ins starting at a root feature. Cross-platform building is also supported. The main benefit of PDE Build is that it brings all this together into one relatively simple process. Developers express their normal runtime dependencies in manifest files and a mapping from development time structure to runtime structure in the feature and plug-in build.properties files. PDE Build does the rest. Key to this process is the automatic generation of the build scripts. Using the input manifests and build.properties, PDE generates Ant scripts that copy identified files and compile identified code using a classpath derived by effectively flattening the plug-in dependency graph. The runtime classpath for a plug-in is defined as a complex graph of plug-in dependencies as described in its manifest file. The classes referenced at runtime are also needed at compile-time, so the compile-time classpath is similarly complex. PDE Build uses the Runtime's plug-in resolution and wiring mechanisms to derive the classpath for each plug-in being built. As we mentioned above, you have already been using PDE Build if you followed along with the feature- or product-exporting examples. When you use these actions, you are under the covers, running PDE Build. The rest of this chapter explores the use of PDE Build in a release engineering setting, where reproducibility and automation are key concerns. |
Recipe 2.4. Using Multiple Struts Configuration Files
Recipe 2.4. Using Multiple Struts Configuration FilesProblemYou want to break SolutionSplit your monolithic struts-config.xml into Example 2-9. Multiple config files (single module)<servlet> When the ActionServlet is loaded, Struts will DiscussionFor anything other than the most trivial applications, the Struts 1.1 introduced support for multiple configuration files. Each Just because you are using multiple configuration files Example 2-10. Multiple config files (multiple modules)<servlet> If you are doing team development, consider splitting your See AlsoRecipe 2.5 details the nuances of using |
Section 23.3. IP Statistics
23.3. IP StatisticsThe Linux kernel keeps several sets of statistics about different events and conditions that can be useful for accounting, debugging, or confirming compatibility with standards. In this chapter, we will only briefly see what statistics are kept by the IP protocol layer (without touching on the SNMP infrastructure) and how they are updated. In previous chapters, especially when describing the various functions, we saw a few cases where macros such as IP_INC_STATS were used to update the value of some counters. Let's start with the data structure that contains all of the counters associated with the IP protocol. It is called ip_statistics and is defined in net/ipv4/ip_input.c. It is a vector with two pointers, each one pointing to a vector of ipstats_mib[*] structures (defined in include/net/snmp.h), one per CPU. The allocation of such vectors is done in init_ipv4_mibs in net/ipv4/af_inet.c.
The ipstats_mib structure is simply declared as an array of unsigned long fields of size _ _IPSTATS_MIB_MAX, which happens to be the size of the IPSTATS_MIB_XXX enumeration list in include/linux/snmp.h. Here is the meaning of the IPSTATS_MIB_XXX values, classified into four groups. For a more detailed description, you can refer to RFC 2011 for IPv4 and RFC 2465 for IPv6. The IPSTATS_MIX_XXX counters that are not used by IPv4 (with the exception of IPSTATS_MIB_INADDRERRORS) are not defined in RFC 2011.
The values of these counters are exported in the /proc/net/snmp file. Each CPU keeps its own accounting information about the packets it processes. Furthermore, it keeps two counters: one for events in interrupt context and the other for events outside interrupt context. Therefore, the ip_statistics array includes two elements per CPU, one for interrupt context and one for noninterrupt context. Not all of the events can happen in both contexts, but to make things easier and clearer, the vector has simply been defined of double in size; those elements that do not make sense in one of the two contexts are simply not to be used. Because some pieces of code can be executed both in interrupt context and outside interrupt context, the kernel provides three different macros to add an event to the IP statistics vector:
The first can be used in either context, because it checks internally whether it was called in interrupt context and updates the right element accordingly. The second and the third macros are to be used for events that happened in and outside interrupt context, respectively. The macros IP_INC_STATS, IP_INC_STATS_BH, and IP_INC_STATS_USER are defined in include/net/ip.h, and the three associated SNMP_INC_XXX macros are defined in include/net/snmp.h. |
22.9. Wrap-Up
|
11.1 Introduction
| [ Team LiB ] |
11.1 IntroductionAll the examples so far in this text have used numeric addresses for the hosts (e.g., 206.6.226.33) and numeric port numbers to identify the servers (e.g., port 13 for the standard daytime server and port 9877 for our echo server). We should, however, use names instead of numbers for numerous reasons: Names are easier to remember; the numeric address can change but the name can remain the same; and with the move to IPv6, numeric addresses become much longer, making it much more error-prone to enter an address by hand. This chapter describes the functions that convert between names and numeric values: gethostbyname and gethostbyaddr to convert between hostnames and IPv4 addresses, and getservbyname and getservbyport to convert between service names and port numbers. It also describes two protocol-independent functions: getaddrinfo and getnameinfo, which convert between hostnames and IP addresses and between service names and port numbers. |
| [ Team LiB ] |
The Java Way of File Operations
| I l@ve RuBoard |
The Java Way of File OperationsInterfaces and classes for dealing with files and other I/O types are in the java.io package. An interface is a class that contains abstract methods. Classes in java.io form a class hierarchy. The two main class types in java.io are text oriented (character streams) and binary oriented (byte streams). Subclasses of the Reader and Writer classes are text oriented; those of the InputStream and OutputStream classes are binary oriented. InputStream and OutputStream are abstract; that is, they can't be instantiated directly. To use an abstract class you must subclass it and instantiate the subclass. The subclasses of InputStream and OutputStream allow the reading of binary data to and from various types of input and output such as byte arrays (memory), files, and even network sockets. Streams can be chained to provide extra functionality. For example, you can buffer a FileInputStream by chaining it to a BufferedInputStream Then you can chain the BufferedInputStream to an ObjectInputStream to read in whole objects at one time. (This is similar to the pickle functionality in Python.) Java 1.1's binary data input streams are complemented by somewhat equivalent text input streams. The parents of these classes are the abstract classes Reader and Writer. Having an equivalent set of text-oriented character stream classes allows the conversion of Unicode text. Readers and Writers, like streams, can be chained together. For example, you can buffer a FileReader by chaining it to a BufferedReader. (Buffering will be explained shortly.) I/O Classes to Be CoveredThere are more than thirty I/O classes, not including interfaces and abstract classes. This seems like a lot, but if you understand how Reader, Writer, InputStream, and OutputStream work, you can easily understand the rest. Reader and Writer subclasses deal with character streams, that is, text. InputStream and OutputStream subclasses deal with binary streams. An easy way to remember this is, if you can read it, use Reader and Writer; if you can't, use InputStream and OutputStream.
|
| I l@ve RuBoard |
Creating the User Module
Creating the User Module
The user module of the online discussion forum application allows end users to view and post discussions. The user module contains three files: UserPage.jsp, AddDiscussion.jsp, and ViewDiscussion.jsp.
Creating the UserPage.jsp File
The UserPage.jsp file of the application provides links that allow an end user to post a new discussion or view the existing discussion topics.
Listing 7-10 shows the content of the UserPage.jsp file:
Listing 7-10: The UserPage.jsp File
<%@page import="TxtConverter.*"%>
<%
/*Call the constructor of DisplayString by passing the value of query parameter, lang*/
DisplayString ds=new DisplayString((session.getAttribute("lang1")).toString());
%>
<html>
<html>
<head>
<meta http-equiv="Content-Language" content="en-us">
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
<title>User Options</title>
</head>
<body>
<!--Display banner heading using getHeading() method of DisplayString class-->
<center><h1 class="style1"><span class="style1"><font
color="#E24907"><%= ds.getHeading()%></font> </h1></center>
<p> </p>
<!--Display page name using getUserOption() method of DisplayString class-->
<p align="center"><b><font face="Arial" size="5" color="#0000FF"><%=
ds.getUserOption() %> </font></b></p>
<table border="0" width="215" cellspacing="0" cellpadding="0" height="1">
<tr>
<!--Display user ID label using getUser() method of DisplayString class-->
<td width="95" bgcolor="#FFFFFF" height="1"><div align="center"><font
color="#FF0000"><b><%= ds.getUser() %>
</b></font></div></td>
<td width="104" bgcolor="#FFFFFF" height="1">
<div align="left"></div>
<!--Display user ID value from session-->
<div align="center"><font
color="#FF0000"><%=session.getAttribute("userid")%></font></div></td>
</tr>
</table>
<div align="justify">
<table border="0" cellpadding="0" cellspacing="0" width="200" style="position: absolute; left: 653px; top: 128px;
width: 259px" >
<tr>
<!--Display logout hyperlink-->
<td height="18" bgcolor="#FFFFFF"><div align="center"><font
color="#FFFFFF"><b><a href="DoLogout.jsp"><%=
ds.getLogout()%></a></b></font></div></td>
</tr>
</table>
</div>
<p align="center"> </p>
<div align="justify">
<table border="0" cellpadding="0" cellspacing="0" width="300" style="position: absolute;
left: 322px; top: 248px; width:
300">
<tr>
<!--Display text for hyperlink to AddDiscussion.jsp using getNewDiscussion() method of
DisplayString class-->
<td bgcolor="#FFFFFF"><div align="center"><i><b><a
href="AddDiscussion.jsp"><%= ds.getNewDiscussion() %>
</a></b></i></div></td>
</tr>
<tr>
<!--Display text for hyperlink to ViewDiscussion.jsp using getViewDiscussion() method of
DisplayString class-->
<td bgcolor="#FFFFFF"><div align="center"><i><b><a
href="ViewDiscussion.jsp"><%= ds.getViewDiscussion()%>
</a></b></i></div></td>
</tr>
</table>
</div>
</body>
</html>
Download this listing.
The above code creates the UserPage.jsp file. This code retrieves the value of the String, lang1, which represents an end user country stored in session, to create an object of the DisplayString class. The code calls the methods of the DisplayString class to provide the user interface. The user interface displays hyperlinks to access the AddDiscussion.jsp and ViewDiscussion.jsp files.
Figure 7-7 shows the user interface of the UserPage JSP in French:
Figure 7-7: User Interface of UserPage JSP in French
Creating the AddDiscussion.jsp File
The AddDiscussion JSP page provides fields that accept end user information about a new discussion. This information is stored in the discussionlist.xml file.
Listing 7-11 shows the content of the AddDiscussion.jsp file:
Listing 7-11: The AddDiscussion.jsp File
<!--Import the necessary packages-->
<%@page import="javax.xml.parsers.*, java.net.*, java.io.*, java.util.*, org.w3c.dom.*"%>
<%@page
import="javax.xml.transform.Transformer, javax.xml.transform.TransformerFactory,javax.xml.
transform.TransformerException"%>
<%@page
import="javax.xml.transform.TransformerConfigurationException,
javax.xml.transform.dom.DOMSource, javax.xml.transform.stream.StreamResult"%>
<%@page import="javax.xml.transform.OutputKeys"%>
<%@page import="TxtConverter.*"%>
<%!String discussionpath;%>
<%
/*Call the constructor of DisplayString by passing the value of query parameter, lang*/
DisplayString ds=new DisplayString((session.getAttribute("lang1")).toString());
%>
<html>
<%
/*Retrieve category, sub category, and discussion content from request object*/
String tempCategory, tempSubCategory,tempDiscussion,tempuser;
tempCategory=request.getParameter("txtCategory");
tempDiscussion=request.getParameter("txtDiscussion");
tempSubCategory=request.getParameter("txtSubCategory");
if(!(tempCategory == null || tempDiscussion == null ||tempSubCategory == null))
{
try{
/*Create a URL object to load properties file*/
URL url=new URL("http://127.0.0.1:8080/seven/path.properties");
/*Open URL connection*/
URLConnection ucon=url.openConnection();
/*Retrieve the discussionlistpath property*/
Properties prop = new Properties();
prop.load(ucon.getInputStream());
discussionpath = prop.getProperty("discussionlistpath");
}
catch(Exception ex)
{
ex.printStackTrace();
}
/*Obtain a DocumentBuilder object by calling the newInstance()method*/
DocumentBuilderFactory docbfact = DocumentBuilderFactory.newInstance();
/*Call the newDocumentBuilder() method to obtain a DocumentBuilder object*/
DocumentBuilder docb = docbfact.newDocumentBuilder();
/*Parse the discussionlist XML document*/
Document doc = docb.parse(new FileInputStream(discussionpath));
/*Obtain Element object that represents the root element of an XML document*/
Element doce = doc.getDocumentElement();
/*Create a new child element*/
Element newe = doc.createElement("query");
/*Obtain user ID from session*/
tempuser=session.getAttribute("userid").toString();
/*Set category, sub category, discussion, and user ID values as attributes to the query Element*/
newe.setAttribute("Category", tempCategory);
newe.setAttribute("Discussion", tempDiscussion);
newe.setAttribute("user",tempuser);
newe.setAttribute("SubCategory", tempSubCategory);
/*Append query Element to root Element*/
doce.appendChild(newe);
/*Obtain a TransformerFactory object*/
TransformerFactory tFactory = TransformerFactory.newInstance();
/*Create a Transformer object*/
Transformer transformer = tFactory.newTransformer();
/*Set output property of Transformer object*/
transformer.setOutputProperty(OutputKeys.INDENT,"yes");
/*Create a DOMSource that acts a transformation source*/
DOMSource source = new DOMSource(doc);
/*Create a StreamResult object that represents a stream to the XML document and acts as the
/*destination of the transformation*/
StreamResult result = new StreamResult(new PrintStream(new
FileOutputStream(discussionpath)));
/*Perform the transformation to transform the document object tree to the XML document*/
transformer.transform(source, result);
}
%>
<!--Display banner heading using resource bundle-->
<Center><H1 class="style1"><font color="#E24907"><%=
ds.getHeading()%></font></H1></Center>
<br><br>
<p align="center"><font size="5"><b>
<!--Display page heading using resource bundle-->
<%= ds.getPostNewQuery() %></b></font>
</p>
<table border="0" width="215" cellspacing="0" cellpadding="0" height="1">
<tr>
<!--Display user ID label using resource bundle-->
<td width="95" bgcolor="#FFFFFF" height="1"><div align="center"><font
color="#FF0000"><b><%= ds.getUser() %>
</b></font></div></td>
<td width="104" bgcolor="#FFFFFF" height="1">
<!--Display user ID value from session-->
<font color="#FF0000"><b>
<%=session.getAttribute("userid")%></b></font></td>
</tr>
</table>
<div align="justify">
<table border="0" cellpadding="0" cellspacing="0" width="200" style="position: absolute;
left: 707px; top: 129px; width: 208px">
<tr>
<!--Display text of hyperlink to DoLogout.jsp using resource bundle-->
<td height="18" bgcolor="#FFFFFF"><font color="#FFFFFF"><b><a
href="DoLogout.jsp"><%= ds.getLogout() %></a></b></font></td>
</tr>
<tr>
<td height="18" bgcolor="#FFFFFF"><b>
<!--Display text of hyperlink to UserPage.jsp using resource bundle-->
<a href="UserPage.jsp">
<%= ds.getGoMain() %></a></b></td>
</tr>
</table>
</div>
<!--Display HTML form to add new discussion-->
<form action = AddDiscussion.jsp method ="post">
<div align="center">
<table width="544" border="1" cellpadding="0" cellspacing="0" bordercolor="#FF0000">
<tr>
<!--Display label for category field using resource bundle-->
<td width="194" bgcolor="#FFFFFF"><div align="center"><%= ds.getCategory()
%></div></td>
<td width="450" bgcolor="#FFFFFF">
<input name="txtCategory" size="30" >
<select name ="txtCategory1" onChange="{form.txtCategory.value=''+this.value+'';}">
<%
java.util.HashMap hmap = new HashMap();
try{
/*Create a URL object to load properties file*/
URL url=new URL("http://127.0.0.1:8080/seven/path.properties");
/*Open URL connection*/
URLConnection ucon=url.openConnection();
/*Retrieve the discussionlistpath property*/
Properties prop = new Properties();
prop.load(ucon.getInputStream());
discussionpath = prop.getProperty("discussionlistpath");
}
catch(Exception ex)
{
ex.printStackTrace();
}
/*Obtain a DocumentBuilder object by calling the newInstance()method*/
DocumentBuilderFactory docbfact = DocumentBuilderFactory.newInstance();
/*Call the newDocumentBuilder() method to obtain a DocumentBuilder object*/
DocumentBuilder docb = docbfact.newDocumentBuilder();
/*Parse the discussionlist XML document*/
Document doc = docb.parse(new FileInputStream(discussionpath));
/*Obtain query elemens of the XML document as a NodeList object*/
NodeList nl1 = doc.getElementsByTagName("query");
/*Iterate through the NodeList object*/
for(int int_1 = 0; int_1 < nl1.getLength(); int_1 ++)
{
/*Obtain the Node object*/
Node n=nl1.item(int_1);
/*Retrieve attributes of the Node objects*/
NamedNodeMap nnp = n.getAttributes();
Object
obj1=hmap.put((nnp.getNamedItem("Category")).getNodeValue(),(nnp.getNamedItem("Category")).getNodeValue());
if(obj1==null)
{
%>
<!--Populate category drop-down list with values of Category attributes-->
<option
value="<%=(nnp.getNamedItem("Category")).getNodeValue()%>"><%=(nnp.getNamedItem
("Category")).getNodeValue()%></option>
<%
}
}
%>
</select>
</td>
</tr>
<tr>
<!--Display label for sub category field using resource bundle-->
<td width="194" bgcolor="#FFFFFF"><div align="center"><%= ds.getSubCategory()
%></div></td>
<td width="450" bgcolor="#FFFFFF">
<input name="txtSubCategory" size="30" >
<select name ="txtSubCategory1"
onChange="{form.txtSubCategory.value=txtSubCategory1.value;}">
<%
hmap = new HashMap();
/*Obtain a DocumentBuilder object by calling the newInstance()method*/
docbfact = DocumentBuilderFactory.newInstance();
/*Call the newDocumentBuilder() method to obtain a DocumentBuilder object*/
docb = docbfact.newDocumentBuilder();
/*Parse the discussionlist XML document*/
doc = docb.parse(new FileInputStream(discussionpath));
/*Obtain query elemens of the XML document as a NodeList object*/
nl1 = doc.getElementsByTagName("query");
/*Iterate through the NodeList object*/
for(int int_1 = 0; int_1 < nl1.getLength(); int_1 ++)
{
/*Obtain the Node object that represents elements of an XML document*/
Node n=nl1.item(int_1);
/*Retrieve attributes of the Node objects*/
NamedNodeMap nnp = n.getAttributes();
Object
obj1=hmap.put((nnp.getNamedItem("SubCategory")).getNodeValue(),(nnp.getNamedItem
("SubCategory")).getNodeValue());
if(obj1==null)
{
%>
<!--Populate category drop-down list with values of SubCategory attributes-->
<option
value="<%=(nnp.getNamedItem("SubCategory")).getNodeValue()%>"><%=(nnp.getNamedItem
("SubCategory")).getNodeValue()%></option>
<%
}
}
%>
</select>
</td>
</tr>
<tr>
<!--Display label for discussion field using resource bundle-->
<td width="194" bgcolor="#FFFFFF"><div align="center"><%= ds.getDiscussion()
%></div></td>
<td width="338" bgcolor="#FFFFFF">
<textarea name="txtDiscussion" rows="4" cols="50"></textarea>
</td>
</tr>
</table>
<p>
<!--Display label for submit button using resource bundle-->
<input type="submit" value=<%= ds.getSubmitQuery() %>>
</p>
</div>
</form>
Download this listing.
The above code creates the AddDiscussion JSP file. The code retrieves the value of the String object, lang1, which is stored in session to create an object of the DisplayString class. The object of the DisplayString class retrieves country-specific data to provide the user interface of the AddDiscussion JSP page. When an end user specifies a topic, sub topic, and content of a discussion, and clicks the Submit button, the code parses the discussionlist.xml file to create an object tree of that file. The code adds a node that corresponds to the discussion in the object tree and transforms the object tree into an XML document. Figure 7-8 shows the user interface of AddDiscussion.jsp in French:
Figure 7-8: User Interface of AddDiscussion.jsp in French
Creating the ViewDiscussion.jsp File
The ViewDiscussion.jsp file displays existing discussions and allows an end user to search for a specific discussion.
Listing 7-12 shows the content of the ViewDiscussion.jsp file:
Listing 7-12: The ViewDiscussion.jsp File
<!--Import the necessary packages-->
<%@page import="javax.xml.parsers.*, java.net.*, java.io.*, java.util.*, org.w3c.dom.*"%>
<%@page
import="javax.xml.transform.Transformer,javax.xml.transform.TransformerFactory, javax.xml.
transform.TransformerException"%>
<%@page
import="javax.xml.transform.TransformerConfigurationException, javax.xml.transform.dom.
DOMSource, javax.xml.transform.stream.StreamResult"%>
<%@page import="javax.xml.transform.OutputKeys"%>
<%@page import="TxtConverter.*"%>
<%!String discussionpath;%>
<%
/*Call the constructor of DisplayString by passing the value of query parameter, lang*/
DisplayString ds=new DisplayString((session.getAttribute("lang1")).toString());
%>
<%
String tempCategory,tempSubCategory;
/*Retrieve values of category and sub category of discussion from request object*/
tempCategory = request.getParameter("txtCategory");
tempSubCategory = request.getParameter("txtSubCategory");
%>
<Center><H1 class="style1"><font color="#E24907"><%=
ds.getHeading()%></font></H1></Center>
<br>
<p align="center"><font size="5"><b>
<%= ds.getQueryList() %> </b></font>
</p>
<table border="0" width="215" cellspacing="0" cellpadding="0" height="1">
<tr>
<td width="95" bgcolor="#FFFFFF" height="1"><div align="center"><font
color="#FF0000"><b><%= ds.getUser() %>
</b></font></div></td>
<td width="104" bgcolor="#FFFFFF" height="1">
<font color="#FF0000"><b>
<%=session.getAttribute("userid")%> </b></font></td>
</tr>
</table>
<div align="justify">
<table border="0" cellpadding="0" cellspacing="0" width="200" style="position: absolute;
left: 713px; top: 115px; width: 207px" >
<tr>
<!---->
<td height="18" bgcolor="#FFFFFF"><font color="#FFFFFF"><b><a
href="DoLogout.jsp"><%= ds.getLogout() %></a></b></font></td>
</tr>
<tr>
<td height="18" bgcolor="#FFFFFF"><b>
<a href="UserPage.jsp">
<%= ds.getGoMain() %></a></b></td>
</tr>
</table>
</div>
<form action = ViewDiscussion.jsp method ="get">
<Center>
<table width="346" border="1" cellpadding="0" cellspacing="0" bordercolor="#FF0000">
<tr>
<td width="179" bgcolor="#FFFFFF"><%= ds.getCategory() %> </td>
<td width="153" bgcolor="#FFFFFF">
<select name="txtCategory" >
<option value=""></option>
<%
HashMap hmap = new HashMap();
try{
/*Create a URL object to load properties file*/
URL url=new URL("http://127.0.0.1:8080/seven/path.properties");
/*Open URL connection*/
URLConnection ucon=url.openConnection();
/*Retrieve the discussionlistpath property*/
Properties prop = new Properties();
prop.load(ucon.getInputStream());
discussionpath = prop.getProperty("discussionlistpath");
}
catch(Exception ex)
{
ex.printStackTrace();
}
/*Obtain a DocumentBuilder object by calling the newInstance()method*/
DocumentBuilderFactory docbfact = DocumentBuilderFactory.newInstance();
/*Call the newDocumentBuilder() method to obtain a DocumentBuilder object*/
DocumentBuilder docb = docbfact.newDocumentBuilder();
/*Parse the discussionlist XML document*/
Document doc = docb.parse(new FileInputStream(discussionpath));
/*Obtain query elemens of the XML document as a NodeList object*/
NodeList nl1 = doc.getElementsByTagName("query");
/*Iterate through the NodeList object*/
for(int int_1 = 0; int_1 < nl1.getLength(); int_1 ++)
{
/*Obtain the Node object*/
Node n=nl1.item(int_1);
/*Retrieve attributes of the Node objects*/
NamedNodeMap nnp = n.getAttributes();
Object obj1 =
hmap.put((nnp.getNamedItem("Category")).getNodeValue(),(nnp.getNamedItem("Category")).
getNodeValue());
if(obj1==null)
{
%>
<!--Populate category drop-down list with values of Category attributes-->
<option><%=(nnp.getNamedItem("Category")).getNodeValue()%></option>
<%
}
}
%>
</select></td>
</tr>
<tr>
<td width="179" height="26" bgcolor="#FFFFFF"><%= ds.getSubCategory() %> </td>
<td width="153" bgcolor="#FFFFFF">
<!--Display sub-category drop-down list-->
<select name="txtSubCategory" >
<option value=""></option>
<%
hmap = new HashMap();
/*Obtain a DocumentBuilder object by calling the newInstance()method*/
docbfact = DocumentBuilderFactory.newInstance();
/*Call the newDocumentBuilder() method to obtain a DocumentBuilder object*/
docb = docbfact.newDocumentBuilder();
/*Parse the discussionlist XML document*/
doc = docb.parse(new FileInputStream(discussionpath));
/*Obtain query elemens of the XML document as a NodeList object*/
nl1 = doc.getElementsByTagName("query");
/*Iterate through the NodeList object*/
for(int int_1 = 0; int_1 < nl1.getLength(); int_1 ++)
{
/*Obtain the Node object that represents elements of an XML document*/
Node n=nl1.item(int_1);
/*Retrieve attributes of the Node objects*/
NamedNodeMap nnp = n.getAttributes();
Object obj1 = hmap.put((nnp.getNamedItem("SubCategory")).getNodeValue(),
(nnp.getNamedItem("SubCategory")).
getNodeValue());
if(obj1==null)
{
%>
<!--Populate category drop-down list with values of SubCategory attributes-->
<option
value="<%=(nnp.getNamedItem("SubCategory")).getNodeValue()%>"><%=
(nnp.getNamedItem("SubCategory")).getNodeValue()%></option>
<%
}
}
%>
</select></td>
</tr>
</table>
<p>
<input type="submit" value= <%= ds.getViewDiscussion() %>">
</p>
</Center>
</form>
<div align="justify">
<table width="300" border="1" cellpadding="0" cellspacing="0" bordercolor="#FF0000"
style="position: absolute; left: 335px; top: 311px; width: 371px;">
<tr bgcolor="#FFFFFF">
<td colspan="3">
<!--Call getExistingDiscussion() method of DisplayString object to display country
specific data-->
<p align="center"><i><font color="#FF0000"><b> <%=
ds.getExistingDiscussion() %> </b></font></i></p>
</td>
</tr>
<tr>
<td width="147" bgcolor="#FFFFFF"><font color="#FF0000"><b> <%=
ds.getCategory() %></b></font></td>
<td width="147" bgcolor="#FFFFFF"><font color="#FF0000"><b><%=
ds.getSubCategory() %></b></font></td>
<td width="147" bgcolor="#FFFFFF"><font color="#FF0000"><b><%=
ds.getDiscussion() %></b></font></td>
</tr>
<%
hmap = new HashMap();
/*Obtain a DocumentBuilder object by calling the newInstance()method*/
docbfact = DocumentBuilderFactory.newInstance();
/*Call the newDocumentBuilder() method to obtain a DocumentBuilder object*/
docb = docbfact.newDocumentBuilder();
/*Parse the discussionlist XML document*/
doc = docb.parse(new FileInputStream(discussionpath));
/*Obtain query elemens of the XML document as a NodeList object*/
nl1 = doc.getElementsByTagName("query");
/*Iterate through the NodeList object*/
for(int int_1 = 0; int_1 < nl1.getLength(); int_1 ++)
{/*If end user does not specifies any search criteria display all discussion data*/
Node n=nl1.item(int_1);
NamedNodeMap nnp = n.getAttributes();
if(tempCategory == null && tempSubCategory == null )
{
%>
<tr>
<td width="147"
bgcolor="#FFFFFF"><%=(nnp.getNamedItem("Category")).getNodeValue()%></td>
<td width="147"
bgcolor="#FFFFFF"><%=(nnp.getNamedItem("SubCategory")).getNodeValue()%></td>
<td width="147"
bgcolor="#FFFFFF"><%=(nnp.getNamedItem("Discussion")).getNodeValue()%></td>
</tr>
<%
}
else
{
if (!(tempCategory.equals("")))
{
if ((nnp.getNamedItem("Category")).getNodeValue().equals(tempCategory))
{
/*If end user specifies a category,match end user category with the node value. For
/* matching categories, display discussion data.*/
%>
<tr>
<td width="147"
bgcolor="#FFFFFF"><%=(nnp.getNamedItem("Category")).getNodeValue()%></td>
<td width="147"
bgcolor="#FFFFFF"><%=(nnp.getNamedItem("SubCategory")).getNodeValue()%></td>
<td width="147"
bgcolor="#FFFFFF"><%=(nnp.getNamedItem("Discussion")).getNodeValue()%></td>
</tr>
<%
}
}
else
{
if (!(tempSubCategory.equals("")))
{
if ((nnp.getNamedItem("SubCategory")).getNodeValue().equals(tempSubCategory))
{
/*If end user specifies both category and sub category,match end user sub
category with the node value. For matching categories, display discussion data.*/
%>
<tr>
<td width="147"
bgcolor="#FFFFFF"><%=(nnp.getNamedItem("Category")).getNodeValue()%></td>
<td width="147"
bgcolor="#FFFFFF"><%=(nnp.getNamedItem("SubCategory")).getNodeValue()%></td>
<td width="147"
bgcolor="#FFFFFF"><%=(nnp.getNamedItem("Discussion")).getNodeValue()%></td>
</tr>
<%
}
}
}
}
}
%>
</table>
</div>
Download this listing.
The above code creates the ViewDiscussion JSP page. The code creates an object of the DisplayString class to display country-specific data. The parse() method of the DocumentBuilder object parses the discussionlist.xml file and retrieves values of the Category and SubCategory attributes. The Topic and Sub Topic drop-down lists display the attribute values. When an end user selects a topic or sub topic to view discussions, the code iterates through the object tree node of the discussionlist XML document to retrieve data of all matching discussions. The code displays the data in the country-specific user interface in a tabular format.
Figure 7-9 shows the user interface provided by the ViewDiscussion JSP page in French:
Figure 7-9: User Interface of the ViewDiscussion JSP Page in French
Selecting the Data as Java Objects
Selecting the Data as Java Objects
You have learned how to use JdbcTemplate to perform raw data operations, but there is too much code to write, and in most cases, writing code for various select statements results in massive code duplication. Also, the queryForList() method returns a List of Map instance because it is not aware of the columns being selected. Unfortunately you still need to write lots of code to retrieve the data using ResultSets and create appropriate domain objects.
In Listing 8-14, we show the use of Spring JDBC support classes. We are going to start the discussion with the use of the MappingSqlQuery class that will process the result set and create appropriate domain objects. Subclasses of MappingSqlQuery must implement its mapRow method. This method gets the values from the underlying ResultSet and returns the appropriate domain object.
Listing 8-14: MappingSqlQuery Usage
package com.apress.prospring.ch10.spring;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.jdbc.object.MappingSqlQuery;
import com.apress.prospring.ch10.TestDao;
import com.apress.prospring.ch10.domain.Test;
public class JdbcTestDao implements TestDao, InitializingBean {
private DataSource dataSource;
private static final String SELECT_BY_NAME_SQL =
"select * from Test where Name=?";
abstract class AbstractSelect extends MappingSqlQuery {
public AbstractSelect(DataSource dataSource, String sql) {
super(dataSource, sql);
}
protected Object mapRow(ResultSet rs, int rowNum) throws SQLException {
Test test = new Test();
test.setName(rs.getString("Name"));
test.setTestId(rs.getInt("TestId"));
return test;
}
}
class SelectByName extends AbstractSelect {
public SelectByName (DataSource dataSource) {
super(dataSource, SELECT_BY_NAME_SQL);
declareParameter(new SqlParameter(Types.VARCHAR));
}
}
public void afterPropertiesSet() throws Exception {
if (dataSource == null) {
throw new BeanCreationException("Must set dataSource on UserDao");
}
// more initialization code
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
The code in Listing 8-14 looks like it can actually do some database work! Let's review what we have implemented.
First, we have the AbstractSelect class that extends MappingSqlQuery. This class maps the ResultSet columns and returns the appropriate Test domain object. All query classes that select users inevitably return User objects, so we can use the AbstractSelect class as a super- class of all select classes. The SelectByName class exemplifies this approach. The constructor takes only a single DataSource parameter, because this class performs one specific SQL query. This particular query requires two VARCHAR parameters, username and password. We declare the parameters using a call to declareParameter().
AbstractSelect is a superclass for all concrete Select implementations that no longer have to implement the mapRow() method to create an instance of the domain object. This implies that all select statements need to return the same set of columns in order for AbstractSelect.mapRow() to be able to create the domain object.
You may be wondering why AbstractSelect is still marked as abstract; after all, it already implements the mapRow() abstract method. There is a simple explanation for this: we should not use the AbstractSelect class to perform any SQL query, because different queries may use different parameters, and by marking this class abstract, we are forcing ourselves to subclass AbstractSelect for every SQL query.
A more important benefit, however, is that by implementing each individual query in its own class, we can take advantage of the fact that most databases precompile the queries and cache the compiled query plan. This may not be very critical in simple select * from Table queries similar to those that result in a full table scan, but it becomes more important as the queries get more complex.
| Note | Some databases do not use the precompiled plans. The rationale behind this is that the statistics used to create the query plan may become out of date very quickly as records are inserted into the database. If a table has 100 rows, for example, it is not worth the effort to use index operations, but if a table has |
1,000,000 rows, then the additional work of an Index Scan operation is well justified. It is not impossible to imagine that a query plan was created for a table of 100 rows and, in the meantime, the number of rows increased to a million. If this was the case, the database would be using a very inefficient query plan, and the query time saved by using the precompiled query plan would be insignificant.
Listing 8-15 shows how to use the concept of the AbstractSelect class in a TestDao implementation.
Listing 8-15: TestDao Interface
package com.apress.prospring.ch10;
import java.util.List;
public interface TestDao {
public List getByName(String name);
}
To implement the method declared in the TestDao interface, we are going to use the SelectByName inner class (see Listing 8-16). To use this class, we need to instantiate, and we will do so in the afterPropertiesSet() method declared in InitializingBean.
Listing 8-16: Implementation of getByName
package com.apress.prospring.data.jdbc;
public class JdbcTestDao implements TestDao, InitializingBean {
private DataSource dataSource;
private static final String SELECT_BY_NAME_SQL =
"select * from Test where Name=?";
abstract class AbstractSelect extends MappingSqlQuery {
public AbstractSelect(DataSource dataSource, String sql) {
super(dataSource, sql);
}
protected Object mapRow(ResultSet rs, int rowNum) throws SQLException {
Test test = new Test();
test.setName(rs.getString("Name"));
test.setTestId(rs.getInt("TestId"));
return test;
}
}
class SelectByName extends AbstractSelect {
public SelectByName(DataSource dataSource) {
super(dataSource, SELECT_BY_NAME_SQL);
declareParameter(new SqlParameter(Types.VARCHAR));
}
}
private SelectByName selectByName;
public void afterPropertiesSet() throws Exception {
if (dataSource == null) {
throw new BeanCreationException("Must set dataSource on testDao");
}
selectByName = new SelectByName(dataSource);
}
public List getByName(String name) {
return selectByName.execute(new Object[] { name });
}
}
This code links all pieces of the puzzle together. We created an abstract subclass of Spring's MappingSqlQuery named AbstractSelect, which takes care of mapping the results retrieved from the database into a Test object. We then subclassed AbstractSelect in SelectByName to execute the SQL query that selects rows from the Test table to return a test whose name matches the parameters passed to the method. In the SelectByName constructor, we have declared that name is the only parameter and is stored as VARCHAR in the database table. To select the Test objects by name, we need to pass the parameter value to the execute() method in an Object[] array containing a single value. The execute() method returns a List of objects returned by the mapRow() method. Because SelectByName is a subclass of AbstractSelect, and because the AbstractSelect.mapRow() method returns an instance of the Test class, we can safely cast the Objects in the List to Tests.
Keep in mind that the order in which the parameters are passed is critical to the execute() method: if we accidentally change the order of the parameters, we do not get the correct results.
Let us step back and look at the code we have written. If this is the only DAO implementation class in the application, it is just fine the way it is. However, most applications have more than one DAO class. If this is the case, we need to create a DAO class that has a dataSource property and implements InitializingBean and the appropriate DAO interface. This has a nasty code smell and as a result, we need to refactor the code. Spring supports this situation by providing a JdbcDaoSupport class.
The refactored JdbcTestDao class is shown in Listing 8-17.
Listing 8-17: Refactored JdbcTestDao
public class JdbcTestDao
extends JdbcDaoSupport implements TestDao, InitializingBean {
private DataSource dataSource;
private static final String SELECT_BY_NAME_SQL =
"select * from Test where Name=?";
abstract class AbstractSelect extends MappingSqlQuery {
/* omitted for clarity */
}
class SelectByName extends AbstractSelect { /* omitted for clarity */ }
private SelectByName selectByName;
protected void initDao() throws Exception {
super.initDao();
selectByName = new SelectByName(getDataSource());
}
public void afterPropertiesSet() throws Exception {
if (dataSource == null)
throw new BeanCreationException("Must set dataSource on testDao");
selectByName = new SelectByName(dataSource);
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public List getByName(String name) {
return selectByName.execute(name);
}
}
This code is much cleaner after we remove the stricken-out lines and add the lines in bold. We had to remove some of the code because JdbcDaoSupport implements InitializingBean's afterPropertiesSet as final, and it also has the dataSource property. Because we still need to instantiate the query inner classes, JdbcDaoSupport allows us to override the initDao() method, which is called after all properties have been set on JdbcDaoSupport. We can access the dataSource property by calling the getDataSource() method.
The code we now have looks like it does not need any more refactoring, and indeed, that would be true if we were only selecting and updating data. Before we take a look at the inevitable catch with inserts, we will discuss data updates.