Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Apache Struts - From Novice To Professional (2006)

.pdf
Скачиваний:
56
Добавлен:
17.08.2013
Размер:
11.68 Mб
Скачать

228 C H A P T E R 1 6 D Y N A M I C F O R M S

The Registration Webapp with Dynamic Forms

In Chapter 5, I introduced the simple Registration webapp, which I expanded on in Chapter 14. In this section, I’ll use a simple variation on the Registration webapp to show you how you might use dynamic forms.

The Registration webapp consists of a single page. This page has one form containing three fields: the user ID, the password, and the password retyped, as shown in Figure 16-1.

Figure 16-1. The Registration webapp main page

In Listing 16-3, I’ve reproduced the JSP code from Chapter 8 for your convenience.

Listing 16-3. registration.jsp

<%@ page contentType="text/html;charset=UTF-8" %> <%@ taglib uri="/tags/struts-bean" prefix="bean" %> <%@ taglib uri="/tags/struts-html" prefix="html" %> <html:html>

<head>

<title><bean:message key="registration.jsp.title"/></title> </head>

<body>

<h1><bean:message key="registration.jsp.heading"/></h1> <html:form action="Registration.do" focus="userid">

<p>

<bean:message key="registration.jsp.prompt.userid"/>

C H A P T E R 1 6 D Y N A M I C F O R M S

229

<html:text property="userid" size="20" /> <html:errors property="userid" />

</p><p>

<bean:message key="registration.jsp.prompt.password"/> <html:password property="password" size="20" /> <html:errors property="password" />

</p><p>

<bean:message key="registration.jsp.prompt.password2"/> <html:password property="password2" size="20" />

</p>

<html:submit>

<bean:message key="registration.jsp.prompt.submit"/> </html:submit>

<html:reset>

<bean:message key="registration.jsp.prompt.reset"/> </html:reset>

</html:form>

</body>

</html:html>

Notice that this is a straight copy of Listing 8-1. I didn’t have to make any changes to the JSP in order to use dynamic forms. As I’ve discussed previously, this is because I haven’t used EL in the tags.

Previously, in Chapter 6, I had to use an ActionForm subclass called RegistrationForm (see Listing 6-1) in order to store and validate the form data. No longer! Since I’ll use DynaValidatorForm and the Validator framework, I no longer need this class.

Listing 16-4 shows how to declare the dynamic form (the changes from Listing 6-1 are in bold font).

Listing 16-4. struts-config.xml for the New Registration Webapp

<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE struts-config PUBLIC

"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">

<struts-config>

<form-beans>

<form-bean name="RegistrationForm"

type="org.apache.struts.validator.DynaValidatorForm">

230 C H A P T E R 1 6 D Y N A M I C F O R M S

<form-property name="userId" type="java.lang.String" /> <form-property name="password" type="java.lang.String" /> <form-property name="password2" type="java.lang.String" />

</form-bean>

</form-beans>

<global-exceptions>

<exception key="reg.error.io-unknown" type="java.io.IOException"

handler="net.thinksquared.registration.ErrorHandler"/>

<exception key="reg.error.unknown" type="java.lang.Exception" path="/errors.jsp" />

</global-exceptions>

<global-forwards>

<forward name="ioError" path="/errors.jsp"/> </global-forwards>

<action-mappings> <action

path="/Registration"

type="net.thinksquared.registration.struts.RegistrationAction"

name="RegistrationForm"

scope="request"

validate="true"

input="/Registration.jsp">

<forward name="success" path="/Success.jsp"/>

</action> </action-mappings>

<message-resources parameter="Application"/>

<plug-in className="org.apache.struts.validator.ValidatorPlugIn" > <set-property property="pathnames"

value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/> </plug-in>

</struts-config>

C H A P T E R 1 6 D Y N A M I C F O R M S

231

The declarations in validations.xml to perform simple validations are exactly as you’ve just seen in Chapter 15 (see Listing 15-5).

I’ll also have to make changes to the RegistrationAction (see Chapter 7, Listing 7-3) in order to correctly read data from the ActionForm passed into execute(). I’ve bolded the changes in Listing 16-5.

Listing 16-5. The New RegistrationAction.java

package net.thinksquared.registration.struts;

import javax.servlet.http.*; import org.apache.struts.action.*;

import net.thinksquared.registration.data.User;

public class RegistrationAction extends Action{

public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response){

//get userid and password (changed!) DynaActionForm dForm = (DynaActionForm) form; String userid = dForm.get("userid");

String password = dForm.get("password");

//complex validation: check if userid exists if(User.exists(userid)){

ActionMessages errors = new ActionMessages(); errors.add("userid",

new ActionMessage("reg.error.userid.exists"));

saveErrors(request,errors);

//navigation: redisplay the user form.

return new ActionForward(mapping.getInput());

}else{

//data transformation: save the userid and password //to database:

232 C H A P T E R 1 6 D Y N A M I C F O R M S

User user = new User(); user.setUserId(userid); user.setPassword(password); user.save();

//navigation: display "you're registered" page

return new ActionForward(mapping.findForward("success"));

}

}

}

Only the section in bold font was changed. The other portions of RegistrationAction are the same as in Listing 7-3.

See Ma, No Hands!: LazyValidatorForm (Struts 1.2.6+)

Version 1.2.6 of Struts saw the introduction of a new type of dynamic form, called LazyValidatorForm. As its name suggests, LazyValidatorForm uses the Validator framework to run validations. You can subclass LazyValidatorForm and write your own validate() function if you want to.

The special thing about LazyValidatorForm is that you don’t have to declare any form properties—not for simple, mapped, or indexed properties. For these properties, LazyValidatorForm will automatically create the necessary simple, mapped, or indexed property if none exists.

Of course, if you’re using a nested property, you’d have to declare it since there’s absolutely no way for LazyValidatorForm to know which bean class to use for that property. The declaration is the same as for conventional dynamic forms.

However, if you wish to declare a property (of any type), you may do so with the usual <form-property> tag. There are three reasons why you might want to do this:

You want to use a different implementation of Map or List that LazyValidatorForm uses internally. You might do this for reasons of efficiency (your code is faster than the implementations LazyValidatorForm uses) or for additional functionality that the default implementations do not have (e.g., logging or debugging).

You want to take advantage of automatic type casting. Data is always stored as String instances unless you explicitly demand otherwise. In order for LazyValidatorForm to know what type to cast a given property, you have to specify its type using <form-property>. By the way, if you specify an array type, LazyValidatorForm will automatically grow that array for you when the form is populated. Neat!

C H A P T E R 1 6 D Y N A M I C F O R M S

233

You don’t want to pass a null List to the Validator framework. If no values are passed by the user for an indexed property, that property will have a value of null. This will cause the Validator framework to throw an exception. The way around this is to declare the indexed property using <form-property>. This tells LazyValidatorForm to create a zero-length array or empty List of the type you’ve declared. This keeps the Validator framework happy.

Listing 16-6 shows how Listing 16-1 would look like if you were using LazyValidatorForm.

Listing 16-6. Declaring a Dynamic Form with LazyValidatorForm

<form-bean name="MyFormBean" type="org.apache.struts.validator.LazyValidatorForm">

<!-- declaring a simple property: The same as

before since the initial value can't be deduced --> <form-property name="MySimpleProp"

type="java.lang.String" initial="Hello" />

<!-- declaring another simple property: Required since we want automatic type casting -->

<form-property name="MySimpleIntProp" type="int" />

<!-- declaring an indexed property: as above, but the size attibute is gone. -->

<form-property name="MyIndexedProp" type="java.lang.String[]" />

<!-- declaring a mapped property: Gone, because MyMappedProperty will be automatically created by LazyValidatorForm when the form data is submitted. -->

<!-- declaring a nested property: The same as before. --> <form-property name="MyNestedProp"

type="com.myco.myapp.MyBean" />

</form-bean>

Now, Listing 16-6 hasn’t changed much from Listing 16-1, but that’s usually not the case. In many cases, automatic type casting isn’t necessary (as it is with LILLDEP’s

234 C H A P T E R 1 6 D Y N A M I C F O R M S

ContactForm), and neither are initial values. In this case, Listing 16-7 shows how we can pare down Listing 16-6.

Listing 16-7. Declaring a Dynamic Form with LazyValidatorForm, Take 2

<form-bean name="MyFormBean" type="org.apache.struts.validator.LazyValidatorForm">

<!-- declaring a simple property: Gone! initial value not needed -->

<!-- declaring another simple property: Gone! automatic type casting not needed -->

<!-- declaring an indexed property: Gone! We're sure the array will be populated -->

<!-- declaring a mapped property: Gone! because MyMappedProperty will be automatically created by LazyValidatorForm when the form data is submitted. -->

<!-- declaring a nested property: The same as before. --> <form-property name="MyNestedProp"

type="com.myco.myapp.MyBean" />

</form-bean>

As you can see, Listing 16-7 is much better. The declaration for the nested property remains because that’s the only way to let LazyValidatorForm know which bean class to use.

Disadvantages of Using LazyValidatorForm

LazyValidatorForm has all the shortcomings of conventional dynamic forms, plus one more: you’ve lost the “firewalling” property of conventional dynamic forms, as I’ll explain shortly.

If form data is submitted to Struts with more properties compared to the declared ones (a larger array data or new properties), then Struts throws an exception. This doesn’t happen with LazyValidatorForm. This means that a malicious user can craft an HTML page filled with huge amounts of fake “data” and submit this to your webapp, and it will be accepted. The problem may be serious if your code unthinkingly dumps this form data directly into a database. If the data takes time to be processed, your webapp could slow down. In an extreme scenario, your webapp could crash.

C H A P T E R 1 6 D Y N A M I C F O R M S

235

This problem doesn’t happen with conventional dynamic forms because Struts knows exactly what properties to expect, as well as the length of those properties—except perhaps for List- or Map-backed properties, but you can avoid using them in the first place. With LazyValidatorForm, you don’t have the option not to use these.

One way to mitigate this problem is to transfer data to JavaBean classes from a LazyValidatorForm instance, using the BeanUtils technique described earlier. This way, only legitimate data gets processed. Of course, this means that you can’t accept mapped or List-backed indexed properties, since doing so would allow a malicious user to give you huge lists of fake data.

The Hidden Power of BeanValidatorForm (Struts 1.2.6+)

The base class of LazyValidatorForm is BeanValidatorForm, which also has interesting uses of its own. This class is a base class of ValidatorForm, and therefore uses the Validator framework for simple validation.

The interesting thing about BeanValidatorForm is that it accepts a JavaBean in its constructor, automatically populating this JavaBean with the user’s form data. Since 1.2.6,

if the type attribute of the <form-bean> isn’t a subclass of ActionForm, the instantiated object is automatically “wrapped” in an instance of BeanValidatorForm by Struts behind the scenes.

To see what this means practically, suppose you were to redo Lab 6 using BeanValidatorForm implicitly. Instead of creating a ContactForm class, you’d first declare the form bean as shown here:

<form-bean name="ContactFormBean" type="net.thinksquared.lilldep.database.Contact"/>

Note that the class referred to by type is not an ActionForm subclass. Struts (since 1.2.6) will automatically instantiate the Contact and insert it into the constructor of BeanValidatorForm. This is an ActionForm subclass, and will automatically populate the fields of Contact.

To retrieve the Contact instance in your Action, you simply call

BeanValidatorForm bForm = (BeanValidatorForm) form;

Contact contact = (Contact) bForm.getInstance();

Easy! Of course, remember that this is only available in 1.2.6 and above.

236 C H A P T E R 1 6 D Y N A M I C F O R M S

Lab 16: Deleting Selected Contacts in LILLDEP

In listing.jsp, we want to put in check boxes at the left of each contact listing, to allow the user to select contacts for deletion. You will implement this functionality with dynamic forms and <html:multibox>.

<html:multibox> is usually used with <logic:iterate> to render a check box at each iteration, hence the name multibox. These check boxes are tied to an indexed property on the form bean. For example:

<form-bean name="myForm" type="org.apache.struts.action.DynaActionForm">

<form-property name="myArray" type="java.lang.String[]" />

</form-bean>

(This snippet uses a dynamic form, but you could also use a regular ActionForm.) The corresponding <html:multibox> usage might be

<logic:iterate id="aBean" name="myBeans"> <html:multibox property="myArray"

value="<%=((MyBean)aBean).myValue() %>"/>

</logic:iterate>

The value attribute on the multibox is the actual value submitted by a check box of the multibox. A value is stored only if the associated check box is selected.

Also notice that with <html:multibox>, there’s no need to use an explicit indexed property (e.g., property="myArray[..]" ) in order to populate the form bean.

Armed with this background, complete the following steps in order to implement the deleting functionality on the full listing page.

Step 1: Declare the SelectionForm Form Bean

SelectionFormBean is the form bean that will hold the IDs of the Contacts selected for deletion. Declare this as a dynamic form. It should have a single array property called selected, of type java.lang.String[].

C H A P T E R 1 6 D Y N A M I C F O R M S

237

Step 2: Amend listing.jsp

listing.jsp needs to display the check boxes to choose Contacts for deletion:

1.Put an <html:form> tag into listing.jsp so that it submits to DeleteContacts.do.

2.Put in an <html:multibox> tag for input into SelectionFormBean. Use a scriptlet if necessary. Extra credit if you use EL-enabled Struts tags to avoid using scriptlets. Hint: The values submitted by the <html:multibox> should be the IDs of the Contacts to be deleted.

Step 3: Create the Action to Delete Contacts

Complete the implementation of DeleteContactsAction, so that it deletes the selected contacts. A couple of hints:

• Use get(String property, int index) to read values from the dynamic form.

This function throws an IndexOutOfBoundsException if the index is larger than the available list of items.

When you’re done, put in a new action mapping for DeleteContacts. success should lead back to Listing.do (not listing.jsp!). As usual, compile, deploy, and test your work.

Useful Links

List of supported types for DynaActionForm: http://struts.apache.org/ struts-doc-1.2.x/userGuide/building_controller.html

LazyValidatorForm JavaDoc: http://struts.apache.org/struts-doc-1.2.7/api/ org/apache/struts/validator/LazyValidatorForm.html

BeanValidatorForm JavaDoc: http://struts.apache.org/struts-doc-1.2.7/api/ org/apache/struts/validator/BeanValidatorForm.html

Apache Commons’ BeanUtils project: http://jakarta.apache.org/commons/ beanutils/