Apache Jakarta and Beyond: A Java Programmeramp;#039;s Introduction [Electronic resources]

Larne Pekowsky

نسخه متنی -صفحه : 207/ 166
نمايش فراداده

19.3. Using Struts

The entry point to struts is a servlet called ActionServlet. This servlet is typically configured in web.xml (see Chapter 17) to handle all URLs ending in ".do," as in the following:

<servlet>
<servlet-name>action</servlet-name>
<servlet-class>
org.apache.struts.action.ActionServlet
</servlet-class>
<init-param>
<param-name>application</param-name>
<param-value>
config.ApplicationResources
</param-value>
</init-param>
<init-param>
<param-name>config</param-name>
<param-value>
/WEB-INF/classes/config/struts-config.xml
</param-value>
</init-param>
<init-param>
<param-name>validate</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>

Chapter 18. The ApplicationResources with the full set of messages used by this little application is shown in Listing 19.1.

Listing 19.1. The application messages
prompt.number1=First number
prompt.number2=Second number
button.save=Add
button.reset=Reset
button.cancel=Cancel
error.calculator.missing1=<li>Please provide a value for the first number</li>
error.calculator.missing2=<li>Please provide a value for the second number</li>
error.calculator.bad1=<li>The first value does not look like a number</li>
error.calculator.bad2=<li>The second value does not look like a number</li>
errors.required={0} is a required field
testform.whuh={0} is improperly formatted

Next the model needs to be defined, which is quite simple and shown in Listing 19.2.

Listing 19.2. The calculator model
package com.awl.toolbook.chapter19;
public class Calculator {
private double number1;
public double getNumber1() {return number1;}
public void setNumber1(double number1) {
this.number1 = number1;
}
private double number2;
public double getNumber2() {return number2;}
public void setNumber2(double number2) {
this.number2 = number2;
}
private double sum;
public double getSum() {return sum;}
public void setSum(double sum) {this.sum = sum;}
public void computeSum() {
sum = number1 + number2;
}
}

This class has simple properties for the two inputs and the resulting sum. It also has a method called computeSum() that will perform the computation. In this case it would be easy enough to have the controller compute the sum and store it by calling setSum(), but that would be inappropriate because the model should be responsible for managing all of its data. Note that nothing in this bean knows anything about taking values from a form or parsing numbers with commas or anything else.

The bean that will directly interface with th187 form is shown in Listing 19.3.

Listing 19.3. The calculator form
package com.awl.toolbook.chapter19;
import java.text.DecimalFormat;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
public class CalculatorForm extends ActionForm {
private String number1;
public String getNumber1() {return number1;}
public void setNumber1(String number1) {
this.number1 = number1;
}
private String number2;
public String getNumber2() {return number2;}
public void setNumber2(String number2) {
this.number2 = number2;
}
public ActionErrors validate(ActionMapping mapping,
HttpServletRequest request)
{
ActionErrors errors = new ActionErrors();
DecimalFormat f     =
new DecimalFormat("###,###.##");
if(empty(number1)) {
errors.add("number1",
new ActionError(
"error.calculator.missing1"));
}   else {
try  {
f.parse(number1);
}   catch (Exception e) {
errors.add("number1" ,
new ActionError(
"error.calculator.bad1"));
}
}
if(empty(number2)) {
errors.add("number2",
new ActionError(
"error.calculator.missing2"));
} else {
try {
f.parse(number2);
} catch (Exception e) {
errors.add("number2",
new ActionError(
"error.calculator.bad2"));
}
}
return errors;
}
private boolean empty(String s) {
return s == null || s.trim().length() == 0;
}
}

The first thing to notice about this class is that it extends a struts class called ActionForm. Instruts terms everything the controller does is considered an Action, and data is made available to an Action via an ActionForm.

The CalculatorForm contains two simple properties to hold the inputs from the form. Notice that these properties are Strings, whereas those in the model are doubles. This makes sense because a calculator can add numbers, but the form should allow the user to enter arbitrary text including representations of numbers with commas.

The CalculatorForm allows the inputs to be validated through the validate() method, which is defined in the ActionForm base class. Setting the validate flag in web.xml instructs struts to call this method when the form is submitted.

The validate() method is passed an ActionMapping object, which is a struts class containing information about the application, and an HttpServlet-Request, which is a part of the servlet specification and contains information about the request. Neither of these parameters is used in this example, but they are available for more sophisticated kinds of validation.

The validate() method itself checks that values have been provided for both inputs and that Java is able to turn the inputs into numbers. This latter test is done by attempting to parse the data by using the DecimalFormat class, which here has been told to allow numbers with commas. For more information about this class and how it is used, consult the JDK documentation.

If a value is missing or malformed, a new ActionError is added to the set maintained by the ActionErrors object, called errors. The exact text of these error messages comes from the file in Listing 19.1, which means it would be possible to report these errors in any language for which such a file had been built.

The errors are returned at the end of the message. Internally struts will check this return value, and if it is empty, it means there were no problems and the form can be processed. Otherwise, the user must be informed of the errors and given the opportunity to fix them.

Now that the form bean is completed, it is time to write the class that will actually implement the action. This is shown in Listing 19.4.

Listing 19.4. The action handler
package com.awl.toolbook.chapter19;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.text.DecimalFormat;
import java.util.Locale;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import org.apache.struts.util.*;
public final class CalculatorAction extends Action {
public ActionForward perform(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException
{
// Populate the input form
if (form == null) {
form = new CalculatorForm();
request.setAttribute(mapping.getAttribute(),
form);
}
CalculatorForm calcForm = (CalculatorForm) form;
// Build the model
Calculator calc        = new Calculator();
calc.setNumber1(getNumber(calcForm.getNumber1()));
calc.setNumber2(getNumber(calcForm.getNumber2()));
calc.computeSum();
// Store the model in the request so the result
// page can get to it
request.setAttribute("calc",calc);
return (mapping.findForward("success"));
}
private double getNumber(String s) {
DecimalFormat d = new DecimalFormat("###,###.##");
try {
Number n = d.parse(s);
return n.doubleValue();
}   catch (Exception e) {
// No need to worry about parse errors, the
// check in the form bean ensures us of that!
}
return 0.0;
}
}

This class extends another struts classActionwhose perform() method will be called after the form bean successfully validates the inputs. This method is invoked with the form bean, the same ActionHandler that was passed to the validate() method, and the request and response. The method ensures there is a valid form bean, constructs an instance of the Calculator model bean, populates it, and then finishes the process by calling computeSum(). In a more complicated example the model bean might come from a database or some other repository rather than being constructed within the action. Finally, the calculator is stored in the request, which sets up everything for the result page to display the sum, and the name of the result page is returned. This name is not hard-coded; rather, it is kept in the ActionMapping under a key called success.

Now that the classes are completed, struts needs to be told how to use them. This is accomplished though the file specified as the config parameter in web.xml. A minimal version of this file is shown in Listing 19.5.

Listing 19.5. The struts configuration file
<?xml version="1.0" encoding="ISO-8859-1"?>
<struts-config>
<form-beans>
<form-bean name="calculatorForm"
type="com.awl.toolbook.chapter19.CalculatorForm"/>
</form-beans>
<action-mappings>
<action path="/calculator"
type="com.awl.toolbook.chapter19.CalculatorAction"
name="calculatorForm"
scope="request"
validate="true"
input="/chapter19/calculator.jsp">
<forward name="success"
path="/chapter19/calc_result.jsp"/>
</action>
</action-mappings>
</struts-config>

The first section defines all the form beans the application will use, which here is just the CalculatorForm just defined. It is given a namecalculatorFormwhich will be used to reference it from JSP pages and elsewhere in the file.

The next section defines the actions that the application will perform. The path attribute defines the URLfor which this action will be taken. Recall that by convention the ActionServlet is configured to handle all URLs ending in ".do," so this clause of the configuration file indicates that CalculatorAction will be invoked when the user accesses "/calculator.do." The name parameter indicates the name of the form class to use, which here is the name given to the CalculatorForm in the previous section. The scope parameter names the scope in which the form bean should be stored. Using the session scope would allow one bean to collect inputs from a number of different forms spread across many JSP pages. This is useful for applications that must collect a great deal of information before they can perform their actions. The validate flags indicates whether the validate() method of the form bean should be called before calling the perform() method of the handler. Finally, the input parameter indicates the JSP file that contains the form. This value is used if there are any errors validating the form, and the user must be sent back to correct them.

The action tag may contain any number of forward tags that give symbolic names to pages. In this example there is only one called success, which matches the name used in the CalculatorAction. Using symbolic names like this makes it much easier to modify the way sites behave. If it was ever decided that after successfully computing a sum the calculator should send the user somewhere other than calc_result.jsp, it would just be necessary to change the configuration file. Neither the Java nor JSP files would need to be altered.

Struts has been configured with two pages: calculator.jsp, which is marked as the input, and calc_result.jsp, which is marked as the success URL. Struts will determine which of these pages is appropriate based on the input it has received and will then use a RequestDispatcher to include the contents of that page. This means that the URL will be calculator.do regardless of whether the user is looking at the input or result pages. This one URL thus "controls" access to these two pages, further justifying the use of the term controller.

The model and controller have now been fully constructed, and struts will even simplify the task of completing the view. The input page is shown in Listing 19.6.

Listing 19.6. The input page
<%@ taglib prefix="bean"
uri="http://jakarta.apache.org/struts/bean" %>
<%@ taglib prefix=l"
uri="http://jakarta.apache.org/strut201" %>
<194>
<head>
<title>Calculator</title>
&l202:base/>
</head>
<body>
&l202:form action="/calculator">
<bean:message key="prompt.number1"/>
&l202:text property="number1"/><br>
<bean:message key="prompt.number2"/>
&l202:text property="number2"/><br>
&l202:submit>
<bean:message key="button.save"/>
<l:submit>
&l202:reset>
<bean:message key="button.reset"/>
<l:reset>
&l202:cancel>
<bean:message key="button.cancel"/>
<l:cancel>
<l:form>
&l202:errors/>
</body>
<194>

will not render any output. If there are errors, the <> will first display the value of the errors.header property from . This renders as a regula200 form tag but ensures that the action points to the right place. In particular, it will ensure that the form gets sent to the ActionSeverlet by pointing the URL at calculator.do. This in turn will allow the servlet to use the name calculator to look up the correct form bean and action handler in the configuration file.

There are then a number of bean:message and <> tags. bean:message simply looks up a message in the resource file from tags render a standar186 input of type text, but in addition struts can use the name of the provided property, plus what it knows about the form bean, to provide values for these fields as the form is rendered.

Consider what will happen if a user provides a value for the first number but leaves the second one blank. As previously noted the validate() method will fail, and the user will be returned to this input form. The <> tag will display the appropriate error message, informing the user to provide a value for the second number. However, because the user already filled in the first number, it would not be friendly to make them fill it out again. The <> tag will be able to get the value for the first number back out of the form bean and make it the default value, so the user will not need to reenter it. Conceptually this is similar to writing

<input
type="text"
name="number1"
value="<c:out value="${calculatorForm.number1}"/>">

The <> tag hides all the details about which bean and property are used, and it is therefore much easier to work with. Struts provides similar tags that handle check boxes, text areas, and all the rest. It even provides tags to handle form submit and reset buttons, as shown at the bottom of Listing 19.6.

This is possible because JSP is invoked by the servlet. The servlet sets up the CalculatorForm bean and places it in the request, so when validation fails and the servlet passes control to calculator.jsp the bean and hence the user's original inputs are still available.

The only remaining piece of the calculator is the result page, which is shown in Listing 19.7.

Listing 19.7. The result page
<%@ taglib prefix="c"
uri="http://java.sun.com/jstl/core" %>
<%@ taglib prefix="bean"
uri="http://jakarta.apache.org/struts/bean" %>
<bean:message key="message.result"/>:
<c:out value="${calc.sum}"/>

There is no need for the page to load the Calculator because this was already done by the CalculatorAction. The page is thus reduced to pure view with no controller or model elements at all.