You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@struts.apache.org by cr...@locus.apache.org on 2000/12/29 20:16:38 UTC
cvs commit: jakarta-struts/src/share/org/apache/struts/util GenericDataSource.java package.html
craigmcc 00/12/29 11:16:37
Modified: src/share/org/apache/struts/util GenericDataSource.java
package.html
Log:
Update developer documentation on the JDBC connection pool and message
resources implementations.
Revision Changes Path
1.2 +81 -6 jakarta-struts/src/share/org/apache/struts/util/GenericDataSource.java
Index: GenericDataSource.java
===================================================================
RCS file: /home/cvs/jakarta-struts/src/share/org/apache/struts/util/GenericDataSource.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- GenericDataSource.java 2000/11/26 05:11:31 1.1
+++ GenericDataSource.java 2000/12/29 19:16:37 1.2
@@ -1,7 +1,7 @@
/*
- * $Header: /home/cvs/jakarta-struts/src/share/org/apache/struts/util/GenericDataSource.java,v 1.1 2000/11/26 05:11:31 craigmcc Exp $
- * $Revision: 1.1 $
- * $Date: 2000/11/26 05:11:31 $
+ * $Header: /home/cvs/jakarta-struts/src/share/org/apache/struts/util/GenericDataSource.java,v 1.2 2000/12/29 19:16:37 craigmcc Exp $
+ * $Revision: 1.2 $
+ * $Date: 2000/12/29 19:16:37 $
*
* ====================================================================
*
@@ -73,15 +73,90 @@
/**
- * Generic data source implementation of the <code>DataSource</code>
+ * <p>Generic data source implementation of the <code>DataSource</code>
* interface. <b>WARNING</b> - This implementation does not know how to
* provide connections with different username/password combinations. It
* always returns connections based on the username and password configured
- * with <code>setUsername()</code> and <code>setPassword()</code>,
- * respectively.
+ * with <code>setUser()</code> and <code>setPassword()</code>,
+ * respectively.</p>
*
+ * <p>The following properties are supported by the standard
+ * <code>GenericDataSource</code> implementation:</p>
+ * <table>
+ * <tr>
+ * <th width="15%">Property</th>
+ * <th width="85%">Description</th>
+ * </tr>
+ * <tr>
+ * <td align="center">autoCommit</td>
+ * <td>Set to <code>true</code> if you want the connections returned to you
+ * by calling <code>getConnection()</code> to be configured in
+ * "auto-commit after every statement" mode. The default value is
+ * <code>true</code>, to conform to JDBC standard conventions.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">description</td>
+ * <td>A short textual description of this data source. This property is
+ * required by the <code>javax.sql.DataSource</code> interface, but is
+ * not used within this implementation.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">driverClass</td>
+ * <td>The fully qualified class name of the JDBC driver to be utilized for
+ * the connections created by this data source. Consult the
+ * documentation for your JDBC driver to identify the value to be
+ * configured for this property.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">maxCount</td>
+ * <td>The maximum number of JDBC connections that will be created by this
+ * data source. This value must be greater than or equal to the value
+ * specified for the <code>minCount</count> property.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">minCount</td>
+ * <td>The minimum number of JDBC connections to establish when this data
+ * source is first opened. This value must be less than or equal to the
+ * value specified for the <code>maxCount</code> property.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">password</td>
+ * <td>The database password used to establish the connections created by
+ * this connection pool, in conjunction with the username specified in
+ * the <code>user</code> property.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">readOnly</td>
+ * <td>Set to <code>true</code> if you want the connections returned to you
+ * by calling <code>getConnection()</code> to be configured for read only
+ * operations. This can result in more efficient database access,
+ * because the database will know it does not need to retain undo logs
+ * for rolling back the transaction. The default value is
+ * <code>false</code>.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">url</td>
+ * <td>The connection URL to be passed to our JDBC driver when establishing
+ * a new connection. The value specified typically starts with
+ * <code>jdbc:</code>, and includes a reference to the host (and,
+ * optionally, the port number) at which the database server is listening
+ * for connections, plus the name of the database to be opened. Consult
+ * the documentation for your JDBC driver to identify the value to be
+ * configured for this property.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">user</td>
+ * <td>The database username used to establish the connections created by
+ * this connection pool, in conjunction with the password specified in
+ * the <code>password</code> property.</td>
+ * </tr>
+ * </table>
+ *
+ * <p>In addition, you can add to the set of <code>Properties</code> passed to
+ * the JDBC driver by calling <code>addProperty()</code>.</p>
+ *
* @author Craig R. McClanahan
- * @version $Revision: 1.1 $ $Date: 2000/11/26 05:11:31 $
+ * @version $Revision: 1.2 $ $Date: 2000/12/29 19:16:37 $
*/
public class GenericDataSource implements DataSource {
1.2 +477 -0 jakarta-struts/src/share/org/apache/struts/util/package.html
Index: package.html
===================================================================
RCS file: /home/cvs/jakarta-struts/src/share/org/apache/struts/util/package.html,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- package.html 2000/12/16 19:01:44 1.1
+++ package.html 2000/12/29 19:16:37 1.2
@@ -14,6 +14,7 @@
<a href="#doc.Messages">[Message Resources]</a>
</div>
+<hr>
<a name="doc.Intro"></a>
<h3>Introduction</h3>
@@ -39,21 +40,497 @@
particular user's preferred language.</li>
</ul>
+<hr>
<a name="doc.Beans"></a>
<h3>Beans and Properties</h3>
<p>FIXME</p>
+<hr>
<a name="doc.JDBC"></a>
<h3>JDBC Connection Pool</h3>
+<h5>Background</h5>
+<p>A large number of web applications require interaction with a relational
+database to access or update persistently stored information. In a typical
+client-server application, each concurrent user opens their own database
+connection at program initialization, and uses this connection throughout
+the period of time the application is open.</p>
+
+<p>While this approach can work well in an environment where the number of
+active users is reasonably fixed, it does not scale well to a web application
+where the number of simultaneous users could be very large. In addition, open
+database connections (even when not actively used) do impose some overhead
+costs, and most web application users (at a given instant) are reviewing the
+contents of a previously generated page (or typing in their next set of input
+information), rather than actively accessing the database.</p>
+
+<p>To deal with this situation, several basic strategies are possible:</p>
+<ol>
+<li>Open a connection on each request, do whatever processing is required,
+ and then close the connection.</li>
+<li>Open a connection for each user, and store it in the user's session.</li>
+<li>Share a "pool" of open connections between all of the application's
+ current users.</li>
+</ol>
+
+<p>The first strategy has the virtue of simplicity - you merely need to open
+a database connection any time you need one, perform the appropriate data
+accesses and updates, and close the connection. However, it suffers from a
+major disadvantage: on most databases, establishing a connection can be very
+time consuming (often requiring multiple seconds of clock time), in order to
+perform a database transaction that might take milliseconds.</p>
+
+<p>Opening a connection per user, as the second strategy suggests, is similar
+to the approach taken with client-server applications described earlier. As
+long as the number of simultaneous users can be controlled at a manageable
+number (such as with many intranet-based applications), this approach is
+feasible. However, it becomes unmanageable when the number of users can climb
+rapidly to very large numbers (as is typical of many Internet-hosted public
+applications), and still requires more overhead than a strategy that would
+share a smaller number of connections.</p>
+
+<p>Connection pooling is an implementation of the third strategy. It is based
+on the assumption that most users of a web application will be interacting
+locally with the last page that was sent to their browser. The number of users
+actually performing a request at any given time is usually a very small
+percentage of the total number of active users, and during request processing
+is the only time that a database connection is required.</p>
+
+<p>Struts provides a simple connection pool class called
+<code>org.apache.struts.util.GenericDataSource</code>. It allows you to
+configure a set of connections (with identical connection parameters) to a
+particular database, using a particular JDBC driver, and then share those
+connections among a number of simultaneously operating threads (such as the
+various request threads that are concurrently active in a servlet container).
+The <code>GenericDataSource</code> class implements the
+<code>javax.sql.DataSource</code> interface from the Java Database Connectivity
+(version 2.0) Standard Extension API, so any programs you use to access it
+should reference this interface, rather than the class name directly.
+That way, you can migrate to a more advanced connection pool implementation
+later, with little or no impact on your application.</p>
+
+<p>For more information about the JDBC 2.0 Standard Extension API, you can
+download the spec (and the corresponding API classes), from
+<a href="http://java.sun.com/products/jdbc">http://java.sun.com/products/jdbc</a>. You can also find pointers to a substantial amount of other information
+about available JDBC drivers, programming tutorials, and so on, at this
+web address.</p>
+
+<h5>Initializing and Finalizing the Connection Pool</h5>
+
+<p>The following instructions show you how to configure the connection pool
+class and use it, from any Java application. As you will see below, the Struts
+controller servlet offers you convenient mechanisms to configure one or more
+connection pools, and make them available to Action classes and JSP pages by
+storing the connection pool instances as servlet context attributes (in JSP
+terms, application-scope beans).</p>
+
+<p>To configure a <code>GenericDataSource</code> instance, you must first
+create one:</p>
+<pre>
+ GenericDataSource dataSource =
+ new GenericDataSource();
+</pre>
+
+<p>Next, you must set the appropriate properties, by calling the corresponding
+JavaBeans property setter methods provided by this class. (See the Javadoc
+API for the <a href="GenericDataSource.html">GenericDataSource</a> class for
+more details on the available properties). An example of configuring the
+connection pool object to a Postgres database might look like this:</p>
+<pre>
+ dataSource.setAutoCommit(false);
+ dataSource.setDescription("My Database Connection Pool");
+ dataSource.setDriverClass("org.postgresql.Driver");
+ dataSource.setMaxCount(4);
+ dataSource.setMinCount(1);
+ dataSource.setPassword("mypassword");
+ dataSource.setUrl("jdbc:postgresql://localhost/mydatabase");
+ dataSource.setUser("myusername");
+</pre>
+
+<p>Finally, you must <code>open()</code> the connection pool. This will
+establish the initial connections to the database (based on the value you have
+configured for the <code>minCount</code> property). As you use connections
+from the pool in multiple threads, additional connections (up to the number
+you specify with the <code>maxCount</code> property) will be created as needed.
+</p>
+<pre>
+ try {
+ dataSource.open();
+ } catch (SQLException e) {
+ ... deal with exception ...
+ }
+</pre>
+
+<p>When you are completely through with the connection pool, you can gracefully
+close all of the currently open database connections by executing</p>
+<pre>
+ try {
+ dataSource.close();
+ } catch (SQLException e) {
+ ... deal with exception ...
+ }
+</pre>
+
+<h5>Using the Generic Connection Pool</h5>
+
+<p>To access the database from within an application class, you must follow a
+simple four-step procedure each time you need a connection:</p>
+<ol>
+<li>Acquire a connection from the connection pool.</li>
+<li>Perform the database operations required by your application.</li>
+<li>Cause the last database transaction to be committed or rolled back
+ (commits are required on many databases to ensure that the database
+ operations you just performed are permanently stored or not).</li>
+<li>"Close" the connection, which returns it to the connection pool for
+ reuse later.</li>
+</ol>
+
+<p>An example code sequence that performs this procedure might look like
+this:</p>
+<pre>
+ DataSource dataSource = ... acquire reference to dataSource ...
+ Connection conn = null;
+ PreparedStatement stmt = null;
+ ResultSet rs = null;
+ try {
+ conn = dataSource.getConnection();
+ stmt = conn.prepareStatement("SELECT cust_id, name FROM customers" +
+ " WHERE (last_purchase_date >= ?)" +
+ " ORDER BY name");
+ stmt.setDate(1, lastPurchaseDate);
+ rs = stmt.executeQuery();
+ while ((row = rs.next()) != null) {
+ ... process this row ...
+ }
+ rs.close();
+ rs = null;
+ stmt.close();
+ stmt = null;
+ conn.commit();
+ conn.close();
+ conn = null;
+ } catch (SQLException e) {
+ if (rs != null) {
+ try {
+ rs.close();
+ } catch (SQLException f) {
+ ;
+ }
+ rs = null;
+ }
+ if (stmt != null) {
+ try {
+ stmt.close();
+ } catch (SQLException f) {
+ ;
+ }
+ stmt = null;
+ }
+ if (conn != null) {
+ try {
+ conn.rollback();
+ } catch (SQLException f) {
+ ... deal with exception ...
+ }
+ }
+ ... deal with exception ...
+ } finally {
+ if (conn != null) {
+ try {
+ conn.close();
+ } catch (SQLException f) {
+ ... deal with exception ...
+ }
+ conn = null;
+ }
+ }
+</pre>
+
+<p>One aspect of the above code example that might surprise developers who
+have previously used JDBC connections individually is the idea of calling
+close() on the Connection. Normally, this call will sever the Connection's
+underlying link to the database, and render that Connection unuseable for
+any further operations. However, when used in a connection pool environment,
+the actual Connection you receive by calling <code>getConnection()</code>
+is a customized "wrapper" around a real JDBC Connection instance. Calling
+<code>close()</code> on this wrapper simply causes this connection to be
+returned to the pool.</p>
+
+<p>What would happen if your application failed to return a connection to the
+pool when it was through? As you might expect, that particular connection
+becomes "lost" to the server, and can never again be used (even though it
+remains connected to the database throughout the life of the connection pool
+itself). If this happens repeatedly, you will eventually exhaust the pool
+of available connections, and application processing will stop.</p>
+
+<p>To avoid this problem, your application logic must ensure that it
+<strong>ALWAYS</strong> returns allocated connections to the pool, no matter
+what problems might happen in the interim. The Java language provides one
+convenient mechanism to achieve this - using a <code>finally</code> block,
+as in the code example above. This is not the only way to ensure that a
+connection is always returned, but it is very convenient.</p>
+
+<h5>Using Connection Pools with the Struts Controller Servlet</h5>
+
+<p>If your application is running underneath the Struts controller servlet
+(<code>org.apache.struts.action.ActionServlet</code>), you can take advantage
+of the servlet's ability to preconfigure one or more connection pools for you,
+based on information included in the <code>struts-config.xml</code> file.
+Simply include a section that looks like this:</p>
+<pre>
+ <data-sources>
+ <data-source
+ autoCommit="false" description="My Database Connection Pool"
+ driverClass="org.postgresql.Driver" maxCount="4" minCount="1"
+ password="mypassword" url="jdbc:postgresql://localhost/mydatabase"
+ user="myusername"
+ />
+ </data-sources>
+</pre>
+
+<p>After being initialized, the connection pools will be stored as servlet
+context attributes under the bean name specified by the <code>key</code>
+attribute. If you did not specify a <code>key</code>, the default key is the
+value of the string constant <code>Action.DATA_SOURCE_KEY</code>. Thus, you
+can access and utilize a connection, from within an <code>Action</code> class,
+like this (for the default data source):</p>
+<pre>
+ DataSource dataSource = (DataSource)
+ servlet.getServletContext().getAttribute(Action.DATA_SOURCE_KEY);
+ conn = dataSource.getConnection();
+ ... perform required functions as in the previous example ...
+ conn.close();
+</pre>
+<hr>
+
<a name="doc.Messages"></a>
<h3>Message Resources</h3>
+<h5>Background</h5>
+
+<p>Modern applications often include the requirement to support multiple
+languages, for users who prefer to interact in a language other than the
+default language configured on the server platform. In addition, sentences
+often need to be constructed, with dynamic content whose placement in the
+message depends on the standard sentence structure in that particular
+language.</p>
+
+<p>The standard Java platform includes a family of classes
+(<code>java.util.ResourceBundle</code>) designed to support looking up message
+strings based on a standard "key". The resource bundle classes automatically
+access a Java class (or properties file) that is named with a naming
+convention that includes the Locale to which messages in that class (or file)
+pertain. However, this selection is based only on the default Locale of the
+server platform, and cannot be adjusted on a per-user basis as required for an
+internationalized web application.</p>
+
+<p>Struts includes a family of classes
+(<code>org.apache.struts.util.MessageResources</code>) that extends the basic
+approach to looking up message strings by key, allowing you to optionally
+specify a Locale along with the key. In this way, you can build applications
+that let your users select which Locale they wish to operate within, and then
+look up messages in that language - using the same message keys no matter what
+language is selected.</p>
+
+<p>In addition to supporting dynamic selection of a Locale for message lookup,
+the <code>MessageResources</code> family of classes optionally allow you to
+specify up to four parameter replacement objects, which are used to replace the
+parameter placeholders "{0}" through "{3}" in the retrieved message. This
+replacement uses the facilities of the standard Java
+<code>java.text.MessageFormat</code> class, which supports many extended
+formatting capabilities as well.</p>
+
+<p>For more information about internationalized messages, consult the following
+resources in your Java Development Kit documentation bundle:</p>
+<ul>
+<li><em>Internationalization Info</em> - General information on Java's standard
+ support for internationalized applications can be found at
+ <code><$JAVA_HOME/docs/guide/internat/index.html></code>.
+ The "Internationalization Overview" section includes useful information
+ about Locales, localized resources, message formatting, and other
+ relevant topics.</li>
+<li><em>Internationalization Tutorial</em> - The Java Language Tutorial has a
+ comprehensive trail covering internationalization, available at:
+ <a href="http://java.sun.com/docs/books/tutorial/i18n/index.html">
+ http://java.sun.com/docs/books/tutorial/i18n/index.html</a>.</li>
+<li><em>Javadoc APIs</em> - You will want to consult the Javadoc API
+ documentation for the following standard Java classes:
+ <ul>
+ <li><code>java.text.MessageFormat</code>
+ <li><code>java.util.ResourceBundle</code>
+ <li><code>java.util.PropertyResourceBundle</code>
+ <li><code>java.util.Properties</code> - See the documentation for the
+ <code>load()</code> method for the valid syntax of properties files
+ that you prepare.</li>
+ </ul></li>
+</ul>
+
+<h5>Using the Standard MessageResources Implementation</h5>
+
+<p>The standard <code>MessageResources</code> implementation provided by the
+Struts library uses Java properties files to initialize message strings, in a
+manner very similar to that supported by the
+<code>java.util.PropertyResourceBundle</code> class. The following steps are
+required to use these facilities in your Java application.</p>
+
+<p>First, prepare a Java properties file for each language (or Locale) in which
+you wish to support your messages. The filenames you use must conform to the
+naming convention for property resource bundles, as described in the
+documentation referenced above. Be sure you use the same message keys in each
+file to identify the same message.</p>
+
+<p>For example, you might prepare files in French, Spanish, and English that
+contain language-specific versions of the word "Hello". The French file would
+be named <code>Messages_fr.properties</code> and contain the following:</p>
+<pre>
+ hi=Bonjour
+</pre>
+<p>while the Spanish and English files would be named
+<code>Messages_es.properties</code> and <code>Messages_en.properties</code>
+respectively. The corresponding message string definitions would say
+<code>hi=Hola</code> and <code>hi=Hello</code> in these files.</p>
+
+<p>Second, place these properties files into the class path for your
+application, exactly as you would with class files themselves. The name
+actually used to load resources will look like a fully qualified Java class
+name (with appropriate package prefixes), so the file should be nested inside
+a directory structure that matches the packaging (either in an unpacked
+directory, or within a JAR file, as appropriate). For example, assume you
+place directory "foo" on your classpath, and stored the above properties files
+in directory "foo/com/mycompany/mypackage". (If you were using a JAR file like
+"foo.jar" instead, the files would be in directory "com/mycompany/mypackage"
+within the JAR file).
+
+<p>Third, initialize a <code>MessageResources</code> object that corresponds
+to the set of properties files for a particular name, within a particular
+package. The easiest way to do this is to initialize a variable in your main
+application class, like this:</p>
+<pre>
+ public static MessageResources messages =
+ MessageResources.getMessageResources("com.mycompany.mypackage.Messages");
+</pre>
+
+<p>Note that the "com.mycompany.mypackage" part of the name matches the package
+directory into which you placed your properties files, and "Messages" is the
+filename prefix for the particular family of properties files supported by this
+<code>MessageResources</code> instance. Depending on your development process,
+you might find it convenient to store all message strings for an entire
+application in a single properties file family, or to have several families -
+in Struts, for example, there is a family of properties files for each Java
+package.</p>
+
+<p>To access a message string with a particular Locale, execute a statement
+like this:</p>
+<pre>
+ Locale locale = ... select the locale to be used ...
+ String message = messages.getMessage(locale, "hi");
+</pre>
+
+<p>In this case, the variable <code>message</code> will contain the message
+string corresponding to the key "hi", in the language that corresponds to the
+locale that was selected.</p>
+
+<p>For an example of message formatting with replaceable parameters, assume
+that the message strings looked like this, instead (only the English version
+is shown - corresponding changes would be made in the other files):</p>
+<pre>
+ hi=Hello {0}
+</pre>
+
+<p>Now, you can personalize the retrieved message like this:</p>
+<pre>
+ Locale locale = ... select the locale to be used ...
+ String name = "Joe";
+ String message = messages.getMessage(locale, "hi", name);
+</pre>
+
+<p>and the marker "{0}" will have been replaced by the specified name (Joe),
+no matter which language is in use. See the JavaDoc API documentation for the
+<code>java.text.MessageFormat</code> class for more advanced uses of the
+parameter replacement mechanism.</p>
+
+<h5>Developing Your Own MessageResources Implementation</h5>
+
+<p>In the above example, we were using the default
+<code>MessageResources</code> implementation supplied by Struts, which uses
+property files to store the message strings. It is also possible to create
+customized mechanisms to retrieve messages (such as loading them on demand
+from a database). The steps required are as follows:</p>
+<ul>
+<li>Create a customized subclass of
+ <code>org.apache.struts.util.MessageResources</code> that implements
+ message lookup operations as you require.</li>
+<li>Create a customized subclass of
+ <code>org.apache.struts.util.MessageResourcesFactory</code> that will
+ create an instance of your custom <code>MessageResources</code> class
+ when the <code>createResources</code> method is called. Note that the
+ "config" argument to this method can be used to select families of
+ messages in any manner appropriate to your needs - you are not required
+ to emulate the "fully qualified Java class name" approach that is used
+ by the standard <code>PropertyMessageResourcesFactory</code> class.</li>
+<li>Tell the <code>MessageResourcesFactory</code> class the name of the
+ customized <code>MessageResourcesFactory</code> implementation to use
+ when creating new factory instances.</li>
+<li>Create a new factory instance.</li>
+<li>Ask the new factory instance to create a <code>MessageResources</code>
+ instance for you.</li>
+</ul>
+
+<p>A code example that illustrates this technique is:</p>
+<pre>
+ MessageResourcesFactory.setFactoryClass("com.mycompany.mypkg.MyFactory");
+ MessageResourcesFactory factory = MessageResourcesFactory.createFactory();
+ MessageResources resources =
+ factory.createResources("configuration information");
+</pre>
+
+<p>Once you have created your custom MessageResources instance, you utilize it
+to access message strings (with or without parameter replacement objects),
+exactly as we illustrated with the standard implementation in the previous
+section.</p>
+
+<h5>Using MessageResources With Struts</h5>
+
+<p>If your application uses the Struts controller servlet, you can optionally
+configure Struts to load an application-specific message resources instance for
+you, and make it available as a servlet context attribute (in JSP terms, an
+application-scope bean). This mechanism is managed by setting the following
+servlet initialization parameters in the web application deployment descriptor:
+</p>
+<ul>
+<li><strong>application</strong> - The configuration string that will be
+ passed to the <code>createResources()</code> method of the message
+ resources factory, in order to identify the family of resources to be
+ supported. If you use the standard message resources factory, this must
+ be the base fully qualified name of the property resources files used
+ to contain these messages, as illustrated above.</li>
+<li><strong>factory</strong> - Fully qualified Java class name of the
+ <code>MessageResourcesFactory</code> to be used. By default, the standard
+ implementation provided by Struts
+ (<code>org.apache.struts.util.PropertyMessageResourcesFactory</code>)
+ will be used.</li>
+</ul>
+
+<p>Struts provides several JSP custom tags that assume the existence of a
+<code>java.util.Locale</code> attribute in the user's session, under the key
+named by the constant string value of <code>Action.LOCALE_KEY</code>. Your own
+application logic can set this attribute at any time, or you can ask Struts to
+set it automatically (if not already set) based on the
+<code>Accept-Language</code> HTTP header included with the request. There are
+two mechanisms by which you request Struts to perform this service:</p>
+<ul>
+<li>To have this service performed on every request submitted to the controller
+ servlet, set the servlet initialization parameter <code>locale</code> to
+ the value <code>true</code> in the application deployment descriptor.</li>
+<li>To have this service performed by a JSP page when it is accessed directly
+ by a user, utilize a <code><form:html ... locale="true" ... /></code>
+ tag at the top of each page.</li>
+</ul>
</body>
</html>