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

Beginning Apache Struts - From Novice To Professional (2006)

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

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

Summary

Dynamic forms are a way for you to create form beans without any Java code.

Dynamic forms work best when you have simple properties, validated with the Validator framework. You are expected to transfer data to a JavaBean for processing.

Dynamic forms may not be used in place of ActionForms in certain situations, particularly when using EL.

LazyValidatorForm is a new (1.2.6) addition to Struts that removes the need to even declare properties in a form bean.

Since 1.2.6, you can use a JavaBean as the type of a <form-bean>, and Struts will wrap the bean in a BeanValidatorForm. This will automatically populate the bean’s data for you.

C H A P T E R 1 7

■ ■ ■

Potpourri

A potpourri (pronounced poh-poo-ri) is a mix of spices and flower petals in a cloth bag, valued for its pleasing aroma. This chapter is similarly a mix of useful Struts techniques, which I hope will help your Struts apps “smell” that much better! In order of appearance (and likelihood that you might find them useful), they are

PropertyUtils: A way to read properties on JavaBeans in general, and ActionForms in particular, without resorting to type conversions.

DownloadAction: An Action subclass that simplifies downloading data to a web client. This data might be dynamically generated by your webapp or a static file on the server.

LocaleAction: Yet another way to allow users to switch locales.

IncludeAction and ForwardAction: Help you to put a Struts front-end to legacy servlets or JSPs.

LookupDispatchAction: Allows you to have multiple actions on your HTML form without using JavaScript.

DispatchAction: A neat way to perform conditional processing in your Action, based on the request URL.

MappingDispatchAction: Helps you group related functionality in one Action.

Global forwards: A second take on how to use global forwards effectively.

Logging: Struts comes bundled with the Apache Commons Logging, which you can use to perform logging.

Wildcards: A useful trick to help cut down the declarations in a struts-config.xml file.

Splitting up struts-config.xml: As a webapp grows, the need to split up struts-config.xml increases. I’ll describe a couple of ways to do this.

239

Note

240

C H A P T E R 1 7 P O T P O U R R I

PropertyUtils

The Apache Commons Beans subproject has some very useful classes (you’ve met BeanUtils in the previous chapter), among them the PropertyUtils class. This class exposes static methods that help you read/write data from/to a JavaBean. The JAR file (commons-beanutils.jar) comes bundled with the Struts binaries, so you can use PropertyUtils right away in your Struts applications.

No type conversions are used, so you don’t have to know the class of the JavaBean at design time. Instead, PropertyUtils uses Java’s introspection feature to accomplish its job.

PropertyUtils actually delegates all its work to PropertyUtilsBean. This latter class does the actual work of parsing the property and introspecting the JavaBean. PropertyUtils simply provides convenient static access to PropertyUtilsBean’s functions (which are not static).

The most useful functions on PropertyUtils are

boolean isReadable(Object bean, String property): Returns true if the given property can be read on the bean, false otherwise. This implies the existence of an appropriate getXXX function.

boolean isWritable(Object bean, String property): Returns true if the given property can be written to the bean, false otherwise. This implies the existence of an appropriate setXXX function.

Class getPropertyType(Object bean, String property): Returns the Class object associated with the given property.

Object getProperty(Object bean, String property): Returns the value of the given property.

setProperty(Object bean, String name, Object value): Writes the given value to the bean.

These functions accept simple, indexed, mapped, nested, or even mixed properties (a combination of the other types of properties). Each of these functions may throw

IllegalAccessException: If the calling code cannot call the associated setXXX or getXXX function, although it exists

NoSuchMethodException: If the required property does not exist on the bean

C H A P T E R 1 7 P O T P O U R R I

241

InvocationTargetException: If the get/set function on the bean throws an Exception

IllegalArgumentException: If either bean or property is null

Using PropertyUtils

This is all very good, but how can PropertyUtils be useful in Struts programming? Recall that your ActionForm subclasses (including your DynaActionForm subclasses) are also JavaBeans. In previous lab sessions, to read (or write) an ActionForm’s properties, we had to perform type conversion, as shown in Listing 17-1.

Listing 17-1. Type Conversion Idiom to Read ActionForm Properties

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

//get userid and password

RegistrationForm rForm = (RegistrationForm) form;

String userid = rForm.getUserId(); String password = rForm.getPassword();

...

Listing 17-1 shows an idiom I’ve used throughout this book. To read the properties in an ActionForm subclass, you must do the following:

Perform a type conversion.

Read the form’s properties.

I’ve used this idiom primarily for the sake of clarity, since it simplifies the examples and lab session code. You should think twice before using it in production code, if maintainability is important to you. This is because there are two big downsides to using type conversion as in Listing 17-1:

No dynamic forms: You can’t easily replace the ActionForm with a dynamic form equivalent. You’d have to amend the Action code. You’ve seen this in Chapter 16, when I discussed dynamic forms, and I ported the Registration webapp to using dynamic forms. Refer to Listing 16-5 to see how you’d have to amend Listing 17-1 in order to work with dynamic forms.

242

C H A P T E R 1 7 P O T P O U R R I

Tight coupling: It ties your Action class too rigidly to your ActionForm class, preventing reuse of the Action with other ActionForms. For example, if you wanted to reuse the Action of Listing 17-1, you’d have to ensure that all the ActionForms passed into its execute() were of type RegistrationForm. In some scenarios, this constraint might be too restrictive.

Instead of this approach, you can use PropertyUtils to circumvent both these drawbacks. PropertyUtils lets you completely decouple your Action from a particular ActionForm subclass (this includes dynamic forms).

Using PropertyUtils, Listing 17-1 would now look like Listing 17-2.

Listing 17-2. Using PropertyUtils to Read ActionForm Properties

import org.apache.commons.beanutils.PropertyUtils;

...

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

throws Exception{

//get userid and password

String userid = PropertyUtils.getProperty(form,"userid"); String password = PropertyUtils.getProperty(form,"password");

...

Using PropertyUtils as shown in Listing 17-2, replacing RegistrationActionForm with an equivalent dynamic form would be much easier—you wouldn’t have to alter your Action subclass code.

In a Nutshell...

One downside of using PropertyUtils is that like with dynamic beans, you lose compiletime checking. You’ve essentially traded compile-time checking for looser coupling between classes.

However, in most instances the advantages gained (looser coupling and the ability to switch to dynamic forms later) well outweigh this loss.

So, unless you have a really good reason, use PropertyUtils to access form or bean properties. Using it consistently makes your code much more maintainable in the long run.

C H A P T E R 1 7 P O T P O U R R I

243

DownloadAction (Struts 1.2.6+)

You might occasionally be required to let users download data from your webapp. If it’s just a static file, this is a nonissue—just put in a link to the file on a web page. However, it is often the case that the data in question is produced on demand. In this case, you’ll have to handle the downloading process yourself.

This isn’t difficult (you need to write a valid HTTP header and then place the data into the HttpServletResponse object that comes with execute()), but it’s a little tedious. The newly introduced (since 1.2.6) DownloadAction class simplifies this work. All you need to supply are

An InputStream containing the data to be downloaded.

A “content type” string (e.g., text/html or image/png) that describes the data’s format. For a list of content type strings, take a look at the “Useful Links” section.

DownloadAction has an inner static interface called StreamInfo:

public static interface StreamInfo {

public abstract String getContentType();

public abstract InputStream getInputStream() throws IOException;

}

that exposes the content type via getContentType(), and the InputStream through getInputStream(). To use DownloadAction, you need to

Implement StreamInfo.

Expose it by subclassing DownloadAction and overriding its getStreamInfo() function.

Put in a form handler (<action> tag) in the struts-config.xml file, associating your DownloadAction subclass with a path. No <forward> element is necessary. (Can you tell why?)

As a double bonus, DownloadAction comes with two inner classes, FileStreamInfo and ResourceStreamInfo, both of which implement the StreamInfo interface.

You can use FileStreamInfo to allow downloading of a static file. The constructor for

FileStreamInfo is

public FileStreamInfo(String contentType, File file)

which is self-explanatory.

ResourceStreamInfo allows users to download a file within a webapp’s root directory:

public ResourceStreamInfo(String contentType,

ServletContext context, String path)

244 C H A P T E R 1 7 P O T P O U R R I

The ServletContext contains information about a webapp’s paths, among other things. You can obtain an instance for your webapp from the HttpSession object:

ServletContext context = response.getSession().getServletContext();

So, all you need to specify are the content type and the relative path to the file to be downloaded.

Of course, in most cases you might prefer to use HTML links instead of FileStreamInfo or ResourceStreamInfo. You’ll find these classes useful if for some reason you can’t expose a link to the file to be downloaded. Listing 17-3 shows an example of how you might subclass DownloadAction, implementing StreamInfo using an anonymous class.

Listing 17-3. Extending DownloadAction package com.myco.myapp.struts;

import java.io.*;

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

import org.apache.struts.actions.DownloadAction;

public class MyDownloadAction extends DownloadAction{

protected StreamInfo getStreamInfo(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)

throws Exception{

/* Get InputStream somehow, as a result of processing request */ InputStream input = ...;

/* Similarly get content type */ String contentType = ...;

return new StreamInfo{

 

public InputStream

getInputStream(){ return input; }

public String

getContentType(){ return contentType; }

}

 

}

 

}

 

C H A P T E R 1 7 P O T P O U R R I

245

You’ll have to create a form handler as well:

<action path="/MyFileDownloadHandler" form="MyDataForm"

type="com.myco.myapp.struts.MyDownloadAction" />

The form bean (MyDataForm) is only necessary if you need to collect user input in order to create the downloadable content. You don’t need a <forward> because the downloaded content is the implicit “next” page.

Exactly what the user sees onscreen would depend on the browser. For example, suppose your download handler delivers a PDF file. If the user clicks the link invoking the handler, the browser might display the PDF file within the browser if there’s an Adobe browser plug-in installed. If there isn’t a plug-in installed, he might navigate to a blank page and get a download dialog box. The exact behavior would depend on the browser involved.

If the user clicks Save As instead of opening the file, then no navigation to a “next” page is performed. The user only gets a download dialog box. For this reason, you should mark such links with the type of data that’s being downloaded so that the user can make the appropriate choice (whether to click the link or Save As).

LocaleAction

Toward the end of Chapter 12, I mentioned that there’s another way to switch locales. The way to do it is to use the Struts class, LocaleAction. Unlike the server-side solution I outlined in Chapter 12, you don’t have to write any Java code in order to use LocaleAction.

Here’s what you need to do:

Implement dynamic forms, one for each locale you want to allow users to switch to. These forms must contain a language property representing the locale’s language code (see Chapter 12). You must set the initial value of this property to the locale’s language code (e.g., jp for Japanese, en for English). You may optionally specify a country attribute for the locale’s country code (see Chapter 12). Again, the initial value of the country property must be set to the country code (e.g., US).

Put in a form handler (<action> tag) for each of these dynamic forms. The single <forward> tag should be named success and must point to the page you want displayed in the new language.

As an example, suppose we want to allow users to switch between English or Japanese. The struts-config.xml file declarations are shown in Listing 17-4.

246

C H A P T E R 1 7 P O T P O U R R I

Listing 17-4. Using LocaleAction

<form-beans>

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

<form-property name="language" type="String" initial="en" /> <form-property name="country" type="String" initial="US" />

</form-bean>

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

<form-property name="language" type="String" initial="jp" /> </form-bean>

... //other form beans

</form-beans>

<action-mappings>

<action path="/ToEnglish" name="English"

type="org.apache.struts.actions.LocaleAction">

<forward name="success" path="/mystartpage.jsp" /> </action>

<action path="/ToJapanese" name="Japanese"

type="org.apache.struts.actions.LocaleAction">

<forward name="success" path="/mystartpage.jsp" /> </action>

... //other form handlers

</action-mappings>

It goes without saying that mystartpage.jsp should be localized using Struts. The alternative is to use ordinary (but localized) HTML or JSP pages for the <forward>s.

C H A P T E R 1 7 P O T P O U R R I

247

To allow users to switch locales, you’d have to put in <html:link>s (see Appendix C) to the relevant form handler:

<html:link action="/ToJapanese"> <bean:message key="prompt.to.japanese" />

</html:link>

<html:link action="/ToEnglish" > <bean:message key="prompt.to.english" />

</html:link>

LocaleAction makes the switch in locales for the user’s session, using the same technique described in Chapter 12 (in the subsection “Switching Locales with a Link”).

IncludeAction and ForwardAction

One compelling reason to use Struts is that it provides a framework for validating user input. This is good news if you have legacy code (business logic) in the form of servlets or JSPs: it’s possible to include a Struts front-end to thoroughly validate user input before it reaches the legacy code.

You can easily roll your own front-end solution, but Struts has one you can use immediately. IncludeAction and ForwardAction make it easy to integrate Struts with legacy servlets or JSPs.

You’d use them like any other Action, with the addition of a parameter attribute in the <action> tag. This parameter attribute is a path pointing to the location of the servlet or JSP that contains the business logic you want to invoke, once Struts has finished validating user input.

For example, suppose your legacy code (a servlet) performs user login, and you want to put a Struts front-end to run simple validations, as in Chapter 6, Listing 6-1 (the Registration webapp). The <action> declaration using IncludeAction and linking LoginForm (to hold and validate user data) with a legacy servlet com.myco.myapp.MyLoginServlet would be

<action path="/Login" type="org.apache.struts.actions.IncludeAction" name="LoginForm"

validate="true"

input="/login.jsp" parameter="/WEB-INF/classes/com/myco/myapp/MyLoginServlet" />

Notice that just as with DownloadAction, you can’t specify a <forward> because the legacy servlet is responsible for producing a “next” page. You can, however, specify exception handlers.