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

Beginning Apache Struts - From Novice To Professional (2006)

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

248

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

INTERNATIONALIZATION

Unfortunately, there’s no easy way for you to internationalize legacy applications with this technique since you have little control over output of legacy code.

If your legacy code is well written, with business logic in servlets and view code in JSPs, then it’s possible to migrate the legacy JSPs to using Struts tags and use IncludeAction or ForwardAction to mediate between Struts and the legacy servlets. This assumes, of course, that the legacy code correctly processes non-ASCII characters. This is often not the case, but if your target languages only use Latin-1, then you might be lucky enough that they do.

The <action> declaration using ForwardAction is similar—you just replace org.apache. struts.actions.IncludeAction with org.apache.struts.actions.ForwardAction.

This begs the question: what is the difference between the two? IncludeAction performs a programmatic “include.” This means two things:

The called legacy code cannot alter cookies or header information sent to the client. The headers and cookies sent to the client are dictated by Struts. Preserving header or cookie information is crucial in some scenarios—for example, when you’ve specified the scope of a request with Struts.

You may subclass IncludeAction to write content to the response object. Of course, you need to call super.execute() at some point to ensure that the legacy code is called. You can do this at any time, either before or after or in between writing content to the response object.

ForwardAction performs a programmatic “forward,” meaning that total control over the data sent to the client is passed to the legacy code. Struts cannot dictate the header or cookie information, and you may not subclass ForwardAction in order to write data to the client, like you could with IncludeAction. ForwardAction simply passes all control of the response object to the legacy code.

In a Nutshell...

In most cases where you need to put a front-end to legacy code, you’d probably use IncludeAction, since this gives you greater flexibility. It’s also useful since you can still write data to the client after the legacy code has sent its output.

ForwardAction is the only way out when the legacy code needs to write header or cookie information in order to work correctly (for example, if it needs to set the content type to something other than text/html).

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

249

LookupDispatchAction

You might sometimes want to put more than one submit button on a single form, each instructing the server-side code to perform a different action with the submitted data.

One common technique to accomplish this is to use JavaScript to set the value of a hidden field on the form, allowing the server-side processing code to tell which submit button was clicked by the user.

As an example, consider the page that follows, which features two submit buttons (Save and Update), a hidden field, and JavaScript to change the hidden field’s value depending on which button was clicked:

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

<head>

<script language="JavaScript">

function changeMode(formName, src){ document.forms[formName].elements["mode"].value = src;

}

</script>

</head>

<body>

<html:form action="MyFormHandler.do">

//... other fields on the form, omitted

<html:hidden property="mode" value="unknown"/>

<html:submit value="Save" onclick="changeMode('MyFormBean','save')" />

<html:submit value="Update" onclick="changeMode('MyFormBean','update')" />

</html:form>

</body>

</html:html>

This homebrew approach has three drawbacks:

250

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

It won’t work if the user has switched off JavaScript.

Each time you had two or more submit buttons, you’d have to duplicate server-side code to dispatch processing according to the value of the <html:hidden> field.

You could create your own Action subclass to do this, but as you’ll see shortly, you’d be reinventing the wheel, and a poorer one at that.

Another irritation is that you’d have to paste or “include” or “import” the JavaScript on each JSP that has this functionality. And you’d have to remember to put in the hidden field and the onsubmit properties for each submit button.

Struts gives you a way to avoid all this unpleasantness. LookupDispatchAction allows you to have multiple actions on your HTML form without having to code any JavaScript.

LookupDispatchAction does its magic by using the fact that when you use <html:submit> with a property attribute, the text displayed on the submit button is passed as a parameter in the URL when the form is submitted. The name of the parameter submitted is the value of the property attribute. This is how LookupDispatchAction knows which submit button was clicked on a form.

Using LookupDispatchAction is easy. We’ll consider the concrete example of having a form with two submit buttons: Print to print the form data and Save to save the data.

The first thing you’ll do is implement the form (see Listing 17-5).

Listing 17-5. Constructing the Form

<html:form action="MyFormHandler.do">

... // form properties here

<html:submit property="action">

<bean:message key="myapp.submit.button.print" /> </html:submit>

<html:submit property="action">

<bean:message key="myapp.submit.button.save" /> </html:submit>

</html:form>

Perhaps the only surprising thing about Listing 17-5 is the use of the property attribute in the <html:submit> buttons. These signal that Struts should pass the text of the submit button (in this case, the value of either myapp.submit.button.print or myapp.submit. button.save) as a parameter in the request URL, with the parameter name of action.

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

251

For example, if the Print button was clicked, the request URL would be

http://..../MyFormHandler.do?action=Print

assuming, of course that the value of myapp.submit.button.print in the user’s currently selected locale is Print.

Note I’ve used property="action" in Listing 17-5, but you can use a different value.

LookupDispatchAction uses this information contained in the URL to tell which button was clicked. In real life, there may be other parameters passed in the URL, so you need to tell LookupDispatchAction which parameter name to use. This is done when you declare the form handler, as shown in Listing 17-6.

Listing 17-6. Declaring the Form Handler

<action path="/MyFormHandler" type="myco.myapp.struts.MyLookupDispatchAction" parameter="action"

...

Listing 17-6 shows how to declare the form handler. The only additional information is the parameter="action", which tells your LookupDispatchAction subclass that it should use the parameter named action in the request URL to tell which submit button was clicked.

Lastly, you need to implement the LookupDispatchAction subclass, which processes the “Print” and “Save” requests. You also have to implement the function getKeyMethodMap(), which tells LookupDispatchAction which function to call when a particular submit button is clicked, as Listing 17-7 shows.

Listing 17-7. Subclassing LookupDispatchAction

import org.apache.struts.actions.LookupDispatchAction;

...

public class MyLookupDispatchAction extends LookupDispatchAction{

/**

*Tells LookupDispatchAction which function to

*use when a given "submit" button is clicked.

*NOTE: This function is called only once by the

*base class's execute(), so there's no need to

*save a copy of the returned map.

**/

252

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

protected Map getKeyMethodMap(){ Map m = new HashMap();

m.put("myapp.submit.button.print","print");

m.put("myapp.submit.button.save","save"); return m;

}

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

throws IOException, ServletException {

//code to print here.

//remember to return the "next" page.

}

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

throws IOException, ServletException {

//code to save here.

//remember to return the "next" page.

}

}

As you can see in Listing 17-7:

getKeyMethodMap() returns a Map instance, which contains the message keys used for each submit button’s text. This message key is associated with the name of the function in your LookupDispatchAction subclass, which needs to be called. Note that getKeyMethodMap() is called only once, so there’s no need to save a copy of the returned Map instance. In fact, the base class automatically saves the returned instance for you.

There are functions, print() and save(), with the same signature as execute(). These do the work of printing and saving the submitted form data. Which function is called depends on the submit button clicked by the user.

There is no implementation of execute(), since we want to implicitly use the base class’s execute() in order to call either print() or save().

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

253

That’s all there is to using LookupDispatchAction, apart from a couple of loose strings:

If the request URL contains no action parameter, then an Exception is thrown. Rather than leave this to chance, you might find it expedient to trap such errors by implementing a function called unspecified(), with the same signature as execute().

You should not declare this function in getKeyMethodMap().

Some forms use a cancel button. You can implement this using <html:cancel> on your form. You can handle a canceled form by implementing a function called cancelled(), with the same signature as execute(). You should not declare this function in getKeyMethodMap().

Using unspecified() and cancelled(), Listing 17-7 could be rewritten as shown in Listing 17-8.

Listing 17-8. MyLookupDispatchAction with unspecified() and cancelled()

import org.apache.struts.actions.LookupDispatchAction;

...

public class MyLookupDispatchAction extends LookupDispatchAction{

//no changes here

protected Map getKeyMethodMap(){ Map m = new HashMap();

m.put("myapp.submit.button.print","print");

m.put("myapp.submit.button.save","save"); return m;

}

public ActionForward print(...)

throws IOException, ServletException {

//code to print here.

}

public ActionForward save(...)

throws IOException, ServletException {

//code to save here.

}

254

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

public ActionForward unspecified(ActionMapping mapping, ActionForm form,

HttpServletRequest request, HttpServletResponse response)

throws IOException, ServletException {

mapping.findForward("page-not-found");

}

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

throws IOException, ServletException { mapping.findForward("main-page");

}

}

LookupDispatchAction is an elegant alternative to using hidden fields for handling forms with multiple actions. It’s also a more robust alternative since it does not depend on JavaScript being enabled on the client’s machine.

DispatchAction

As the name implies, DispatchAction is the base class of LookupDispatchAction, and it performs method dispatching on a URL parameter.

The most common usage of DispatchAction is to call it from an HTML link, with the parameters embedded in the link. As with LookupDispatchAction, dispatching is done using the parameter name declared in the <action> tag using the parameter attribute:

<action path="/MyHandler" type="myco.myapp.struts.MyDispatchAction" parameter="command"

...

And again, for each possible value of the command parameter, there has to be a corresponding function. The difference here with LookupDispatchAction is that there is no getKeyMethodMap(). This is because the values of command in the URL must correspond exactly with functions defined in the DispatchAction subclass.

As a concrete example, suppose command can take the value detailed or summary (detailed or summarized versions of data for the user to view). The links might be

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

255

<a href="MyHandler.do?command=detailed&id=35 >View Details</a> <a href="MyHandler.do?command=summary&id=35 >View Summary</a>

Your subclass of DispatchAction must implement the functions detailed() and summary(), with the same signature as execute(). You should not override execute(), and you may implement unspecified() and cancelled() if you wish, as Listing 17-9 shows.

Listing 17-9. MyDispatchAction

import org.apache.struts.actions.DispatchAction;

...

public class MyDispatchAction extends DispatchAction{

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

throws IOException, ServletException {

//code for detailed view here.

}

public ActionForward summary(...) throws IOException, ServletException {

//code for summarized view here.

}

public ActionForward unspecified(...) throws IOException, ServletException {

mapping.findForward("page-not-found");

}

}

MappingDispatchAction

This Action subclass helps you group related functionality in one Action. Like

LookupDispatchAction, MappingDispatchAction is also a DispatchAction subclass. Like the former two classes, you also have to subclass the base class (MappingDispatchAction) in order to use it.

256

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

Unlike LookupDispatchAction or DispatchAction, MappingDispatchAction does not dispatch on a request parameter. Instead, you declare form handlers with the same MappingDispatchAction subclass to perform different actions.

As an example, suppose you wanted to group slightly different printing functionality in your webapp: Print to PDF, Print to HTML, and Print to Text. The Action subclass handling printing would be responsible for obtaining the necessary data and sending it off to appropriate helper classes in order to print to the right format. In this scenario, it would be unnatural to have the data be handled by three different Actions, since the code is likely to be very similar. You’d want to have one PrintingAction with three functions, one for each print format.

To do this, you declare three <action>s, one for each print format, as shown in Listing 17-10.

Listing 17-10. Multiple Declarations for PrintingAction

<action path="/PrintToPDF" type="myco.myapp.struts.PrintingAction" parameter="pdf"

...

<action path="/PrintToHTML" type="myco.myapp.struts.PrintingAction" parameter="html"

...

<action path="/PrintToText" type="myco.myapp.struts.PrintingAction" parameter="text"

...

The parameter attributes in Listing 17-10 point to actual function names on PrintingAction (see Listing 17-11).

Listing 17-11. PrintingAction

import org.apache.struts.actions.MappingDispatchAction;

...

public class PrintingAction extends MappingDispatchAction{

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

throws IOException, ServletException {

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

257

//code for printing to PDF here.

}

public ActionForward html(...)

throws IOException, ServletException {

//code for printing to HTML here.

}

public ActionForward text(...)

throws IOException, ServletException {

//code for printing to text here.

}

public ActionForward unspecified(...) throws IOException, ServletException {

mapping.findForward("page-not-found");

}

}

The signatures of pdf(), html(), text(), and unspecified() are the same for Action’s execute().

Note As with other DispatchActions, you do not override execute().

As you should know by now, unspecified() handles the case where the requested function does not exist on PrintingAction. (Question: how can this happen?)

In a Nutshell...

As you might have noticed, MappingDispatchAction and DispatchAction are very similar. Certainly, both dispatch according to the parameter on the URL. The difference is how you use them:

MappingDispatchAction: Use this when you want to define multiple form handlers for the same Action.

DispatchAction: Use this if for any reason you don’t want to define multiple form handlers (e.g., to make struts-config.xml more manageable).