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

Beginning Apache Struts - From Novice To Professional (2006)

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

258

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

In most cases, choosing between them is largely a matter of taste. My preference is to use MappingDispatchAction throughout my apps because you can immediately tell what functionality is available on the associated Action, based on the declarations in struts-config.xml.

Using Global Forwards

In Chapter 9, I showed you how to declare global forwards. These forwards are accessible anywhere within the struts-config.xml file, and from <html:link>s. The latter is where global forwards come into their own.

Instead of hard-coding paths in your <html:link>s, you can use global forwards instead. As an example, many webapps have common static navigational links. The LILLDEP

main page is a good example. There are a few navigational links: Full, MNC, Listing, Import, and Collect. Instead of hard-coding these like

<a href="Listing.do">...

<a href="import.jsp">...

...

you should use

<html:link forward="listing">...

<html:link forward="import">...

...

and declare the global forwards as

<global-forwards>

<forward name="listing" path="/Listing.do"/> <forward name="import" path="/import.jsp"/>

...

</global-forwards>

The advantage with this approach is that should you move a JSP or HTML page from one location to another, you don’t have to amend every link to that page. You only need to change the global forward’s path. This greatly improves the maintainability of your webapp.

Logging

Struts uses the Apache Commons Logging interface for logging. This is a unified interface to a number of logging systems.

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

259

When it’s started, Commons Logging locates the best logging system for your installation (Log4j or the logging facilities of Java 1.4), and if neither is present, it uses the default

SimpleLog class.

Note Log4j belongs to the Apache Logging subgroup. It is a very widely used open source logging system.

See “Useful Links” for a URL.

You create logger instances per class—that’s per class, not per instance of a class. For example, in order to log messages in a particular Action subclass, use the code in Listing 17-12.

Listing 17-12. Logging with Apache Commons Logging

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

...

public class MyAction extends Action{

private static Log log = LogFactory.getLog(MyAction.class);

public ActionForward execute(...){

try{

//some processing code

}catch(IOException ioe){ //log the exception if(log.isErrorEnabled()){

log.error("IO Exception occurred!",ioe);

}

}

}

}

LogFactory is a helper class used to create a Log instance, given the Class object associated with MyAction:

LogFactory.getLog(MyAction.class)

The returned value is a Log instance, which is a wrapper around the actual underlying logging class (Log4j or the JDK 1.4 logger or SimpleLog). It has a number of functions you can use to log different messages:

260

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

trace(): Used to log very detailed information

debug(): Used to log a debugging message

info(): Used to signal “interesting” events at runtime, like startup or shutdown

warn(): Used to show that something just short of an error has occurred

error(): Used to flag an exception thrown during processing

fatal(): Used to signal a fatal error, causing premature system shutdown

Each of these functions has two forms. The first (using trace() as an example)

trace(Object message)

prints out the given message, while the second form

trace(Object message, Throwable ex)

allows you to print an exception in addition to a message. The actual message finally logged depends on the underlying logging system used. Remember that Commons Logging is just a wrapper around the logging systems that do the real work.

Each of the functions listed here also represents a priority level: “trace” has lowest priority and “fatal” the highest. This priority system makes it possible to be selective about what messages to log from which class or package.

For example, Log4j allows you to define (using a properties file) exactly what priority messages are allowed to be logged by a given class or package, as shown in Listing 17-13.

Listing 17-13. Some Settings for Log4j

log4j.logger.com.myco.myapp=INFO

log4j.logger.com.myco.myapp.struts.database.Database=ERROR

The setting in Listing 17-13 means that the classes in the package com.myco.myapp are allowed to log fatal, error, warn, and info messages, but not debug or trace ones. The second line means that the Database class is only allowed to log fatal or error messages.

I’d like to stress that Listing 17-13 is merely illustrative, and does not apply to every logging system but just to Log4j. Each underlying logging system will have to be configured differently, and this cannot be controlled by Commons Logging. Commons Logging is only a wrapper around some properly configured logging system.

You should look up the documentation for the logging system you’re using in order to determine how to configure it. Commons Logging only buys you portability. It doesn’t make setting up the logging system simpler.

Now, because some classes may be configured with logging priorities below a given level, it makes sense to detect this first, before attempting a logging. This is mainly

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

261

because a call to a logging function (trace(), debug(), etc.) usually involves creating extra system resources. To avoid this unnecessary overhead, it’s best to test first if logging at the desired level is allowed. The code:

if(log.isErrorEnabled()){

log.error("IO Exception occurred!",ioe);

}

checks first that MyAction has logging clearance including and above error.

In a Nutshell...

Commons Logging is an easy-to-use, portable way for you to perform logging in your Struts (or other) applications. Use it rather than a given logging solution directly.

Using Wildcards

Wildcards are a way to cut down repetition in your struts-config.xml, as long as your webapp has some regular structure. For example, consider Listing 17-14, a variant of the declared <action>s corresponding to the Full and MNC pages for LILLDEP.

Listing 17-14. A Variation on the LILLDEP Declarations for Full and MCN Page Handlers

<action path="/ContactFormHandler_full" type="net.thinksquared.lilldep.struts.ContactAction" name="ContactFormBean"

scope="request"

validate="true"

input="/full.jsp">

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

<action path="/ContactFormHandler_mnc" type="net.thinksquared.lilldep.struts.ContactAction" name="ContactFormBean"

scope="request"

validate="true"

input="/mnc.jsp">

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

262

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

There’s an obvious repeated structure in Listing 17-14. Using wildcards you can cut down the two declarations to just one, as shown in Listing 17-15.

Listing 17-15. LILLDEP Declarations Take 2

<action path="/ContactFormHandler_*" type="net.thinksquared.lilldep.struts.ContactAction" name="ContactFormBean"

scope="request"

validate="true" input="/{1}.jsp">

<forward name="success" path="/{1}.jsp"/> </action>

The * in the path is a wildcard that matches zero or more characters excluding the slash (/) character. You’d use ** to include the slash. You must make this distinction because the slash has a special meaning when used in paths (as you’ll see in the next subsection).

In Listing 17-15, I’ve used just one wildcard. It’s possible to use more than one; for example:

<action path="/*FormHandler_*" ...

would match paths like ContactFormHandler_full or SearchFormHandler_mnc. You’d access the matched strings using {1} and {2}. With ContactFormHandler_full, {1} would equal Contact and {2} would equal full. In all, you are allowed nine wildcards.

Note The {0} wildcard returns the full request URI.

If more than one <action> matches a request, then the last one declared in struts-config.xml is used. There’s an exception to this rule: if an <action> contains no wildcards and it matches a request, it is always used.

In a Nutshell...

When you name your <action>s, take the effort to name them with an eye to future wildcard use. Don’t go overboard with wildcards, though, because they make your system less manageable.

A good rule to follow is to use at most one wildcard per <action>.

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

263

Splitting up struts-config.xml

As your project grows, and especially if you work in a team, you might find yourself wishing that it were possible to break up strutsconfig.xml into several pieces. There are a few reasons why you might want to do this:

Manageability: As the config.xml file becomes too big, the control flow of your webapp becomes less transparent. You’d want to break up struts-config.xml into separate sections, each of which perhaps represents control flow of a limited portion of the overall webapp.

Separate namespaces: You have two or more teams working on different aspects of the webapp, and you’d like these teams to work independently of each other. The primary concern here is to give each team different namespaces. Distinct namespaces would help avoid the problem of two teams creating two different form beans with the same name.

Source control: Some poorly designed source control systems allow only single checkouts. If you’re working in a large team, this is potentially very restrictive, since it’s likely that more than one person might want to edit struts-config.xml at the same time.

The first way to split up struts-config.xml is to simply do just that. You can create multiple struts-config.xml files (with different names, of course!) and instruct Struts that they are to be regarded as one file. To do this, you have to edit web.xml, the servlet configuration file.

For example, suppose you had multiple configuration files: struts-config.xml, struts-config-admin.xml, and struts-config-logon.xml. Listing 17-16 shows how you’d declare them both in web.xml. All you need to do is to put a comma between each Struts configuration file. Easy!

Listing 17-16. Declaring Multiple Struts Configuration Files in web.xml

<servlet> <servlet-name>action</servlet-name> <servlet-class>

org.apache.struts.action.ActionServlet </servlet-class>

<init-param> <param-name>config</param-name>

264

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

<param-value> /WEB-INF/struts-config.xml, /WEB-INF/struts-config-admin.xml, /WEB-INF/struts-config-logon.xml

</param-value>

</init-param> </servlet>

Now, this approach solves manageability and source control issues, but it does not solve namespace problems because Struts will internally merge together all the declared configuration files. So, although they appear as two or more files to you, they appear as one to Struts.

There is a Struts feature called modules that allows you to split a Struts configuration files into different modules. A module represents a logical split in your webapp. Each module gets its own namespace, so this solves namespace issues. Using the previous example, suppose we want to give each Struts config file separate namespaces: the default namespace for items declared in struts-config.xml, the admin namespace for struts-config-admin.xml, and the logon namespace for struts-config-logon.xml. The resulting declaration in web.xml appears in Listing 17-17.

Listing 17-17. Declaring Multiple Submodules in web.xml

<servlet> <servlet-name>action</servlet-name> <servlet-class>

org.apache.struts.action.ActionServlet </servlet-class>

<init-param>

<!-- THE DEFAULT SUB-MODULE -->

<param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</param-value>

</init-param> <init-param>

<!-- THE "ADMIN" SUB-MODULE -->

<param-name>config/admin</param-name> <param-value>/WEB-INF/struts-config-admin.xml</param-value>

</init-param> <init-param>

<!-- THE "LOGIN" SUB-MODULE -->

<param-name>config/logon</param-name> <param-value>/WEB-INF/struts-config-logon.xml</param-value>

</init-param> </servlet>

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

265

With the declaration in Listing 17-17, we may declare two different form beans with the same name in two different submodules. The same applies to <action>s, <forward>s, and other configuration elements.

Access to <action>s and <forward>s would differ between submodules. For example, suppose we had an <action> declared in the admin submodule:

<action path="Listing.do" ...

Then, you’d refer to it as /admin/Listing.do in your <html:link>s or <html:form>s.

Note If you had the same <action> defined in the default submodule, then you’d use /Listing.do.

This brings up an important issue: in any well-designed application, you’re likely to have shared declarations like shared global <forward>s or form beans. When you split your app using multiple submodules, these are not shared between modules. To overcome this problem, you should group together shared declarations in one Struts configuration file.

For example, continuing our earlier example, let’s call this new file struts-config-shared.xml. Listing 17-18 shows what you would do in order to share the declarations in this file across submodules.

Listing 17-18. Using a Shared Struts Configuration File Across Submodules

<servlet> <servlet-name>action</servlet-name> <servlet-class>

org.apache.struts.action.ActionServlet </servlet-class>

<init-param>

<!-- THE DEFAULT SUB-MODULE --> <param-name>config</param-name>

<param-value> /WEB-INF/struts-config-shared.xml, /WEB-INF/struts-config.xml

</param-value>

</init-param> <init-param>

<!-- THE "ADMIN" SUB-MODULE --> <param-name>config/admin</param-name>

266

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

<param-value> /WEB-INF/struts-config-shared.xml, /WEB-INF/struts-config-admin.xml

</param-value>

</init-param> <init-param>

<!-- THE "LOGIN" SUB-MODULE --> <param-name>config/logon</param-name> <param-value>/WEB-INF/struts-config-logon.xml</param-value>

</init-param> </servlet>

In Listing 17-18, I’ve shared struts-config-shared.xml across the default and admin subapplications. This uses the same trick as the multiple configurations file technique described earlier. You just have to separate the filenames with a comma.

In a Nutshell...

There are two ways to split up your Struts configuration file. Use the multiple files approach if your concerns are only about manageability or source control. Use the multiple submodules approach if you need separate namespaces as well.

In the latter, you can declare common form beans, actions, forwards, and so forth in a single file and share them between submodules. This prevents duplication of declarations while giving you separate namespaces.

Useful Links

A compendium of commonly used content type (MIME types) strings: http:// www.utoronto.ca/webdocs/HTMLdocs/Book/Book-3ed/appb/mimetype.html

Apache Commons Logging: http://jakarta.apache.org/commons/logging/

Log4j: http://logging.apache.org/log4j/docs/

Summary

Struts provides a number of useful classes, features, and tricks to make writing webapps easier. This chapter covers some of the ways you can take advantage of what’s available rather than reinventing the wheel in your webapps.

C H A P T E R 1 8

■ ■ ■

Review Lab:

The Collection Facility

In this lab session, you’ll implement a facility to group together contacts into logical collections. This is essentially a tool to classify and manage contacts within LILLDEP.

There are four parts in this Collection facility:

The main Collect page displays a list of defined collections as links. When a link is clicked, the user is given a listing of Contacts within that collection. The Collect page also allows new collections to be defined. Users access this page through the Collect navigation button (see Figure 18-1).

The New Collection page prompts the user for a collection name, query, and memo. This page results in a listing of the newly defined collection’s Contacts.

The Collection Listing page lists the Contacts within a collection. It also allows the user to add an arbitrary Contact from a full listing or click the company name to get into the Collection Full Display page.

The Collection Full Display page displays the Contact’s details, with Previous and Next buttons to navigate up and down the collection.

There are two database tables used to store data for a single collection. The collection table stores the collection’s name, memo, and autogenerated collection ID. The collection_map table holds the Contact ID and collection ID as pairs.

267