JBossWeb SVN: r1512 - branches/2.1.x/webapps/docs.
by jbossweb-commits@lists.jboss.org
Author: jfrederic.clere(a)jboss.com
Date: 2010-07-29 05:28:24 -0400 (Thu, 29 Jul 2010)
New Revision: 1512
Modified:
branches/2.1.x/webapps/docs/changelog.xml
Log:
Arrange doc for 2.1.9
Modified: branches/2.1.x/webapps/docs/changelog.xml
===================================================================
--- branches/2.1.x/webapps/docs/changelog.xml 2010-07-27 13:16:58 UTC (rev 1511)
+++ branches/2.1.x/webapps/docs/changelog.xml 2010-07-29 09:28:24 UTC (rev 1512)
@@ -16,7 +16,7 @@
<body>
-<section name="JBoss Web 2.1.8.GA (remm)">
+<section name="JBoss Web 2.1.9.GA (remm)">
<subsection name="Catalina">
<changelog>
<fix>
@@ -27,6 +27,23 @@
<subsection name="Coyote">
<changelog>
<fix>
+ Fix possible NPE (CVE 2010-2227). (remm)
+ </fix>
+ </changelog>
+ </subsection>
+</section>
+
+<section name="JBoss Web 2.1.8.GA (remm)">
+ <subsection name="Catalina">
+ <changelog>
+ <fix>
+ Info leak. (CVE 2010-1157) (remm)
+ </fix>
+ </changelog>
+ </subsection>
+ <subsection name="Coyote">
+ <changelog>
+ <fix>
<jira>165</jira>: Correct fix for <bug>42727</bug>. (remm)
</fix>
</changelog>
13 years, 9 months
JBossWeb SVN: r1511 - in trunk: webapps/docs and 1 other directory.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2010-07-27 09:16:58 -0400 (Tue, 27 Jul 2010)
New Revision: 1511
Added:
trunk/java/org/apache/catalina/filters/ExpiresFilter.java
Modified:
trunk/java/org/apache/catalina/filters/CsrfPreventionFilter.java
trunk/java/org/apache/catalina/filters/LocalStrings.properties
trunk/webapps/docs/changelog.xml
Log:
- Port mod_expires filter.
- CSRF filter fixes.
Modified: trunk/java/org/apache/catalina/filters/CsrfPreventionFilter.java
===================================================================
--- trunk/java/org/apache/catalina/filters/CsrfPreventionFilter.java 2010-07-21 12:14:34 UTC (rev 1510)
+++ trunk/java/org/apache/catalina/filters/CsrfPreventionFilter.java 2010-07-27 13:16:58 UTC (rev 1511)
@@ -18,6 +18,7 @@
package org.apache.catalina.filters;
import java.io.IOException;
+import java.security.SecureRandom;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -25,6 +26,7 @@
import java.util.Set;
import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
@@ -44,11 +46,13 @@
*/
public class CsrfPreventionFilter extends FilterBase {
- private final Random randomSource = new Random();
+ private String randomClass = SecureRandom.class.getName();
+
+ private Random randomSource;
private final Set<String> entryPoints = new HashSet<String>();
- private final int nonceCacheSize = 5;
+ private int nonceCacheSize = 5;
/**
* Entry points are URLs that will not be tested for the presence of a valid
@@ -67,6 +71,52 @@
}
}
+ /**
+ * Sets the number of previously issued nonces that will be cached on a LRU
+ * basis to support parallel requests, limited use of the refresh and back
+ * in the browser and similar behaviors that may result in the submission
+ * of a previous nonce rather than the current one. If not set, the default
+ * value of 5 will be used.
+ *
+ * @param nonceCacheSize The number of nonces to cache
+ */
+ public void setNonceCacheSize(int nonceCacheSize) {
+ this.nonceCacheSize = nonceCacheSize;
+ }
+
+ /**
+ * Specify the class to use to generate the nonces. Must be in instance of
+ * {@link Random}.
+ *
+ * @param randomClass The name of the class to use
+ */
+ public void setRandomClass(String randomClass) {
+ this.randomClass = randomClass;
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ // Set the parameters
+ super.init(filterConfig);
+
+ try {
+ Class<?> clazz = Class.forName(randomClass);
+ randomSource = (Random) clazz.newInstance();
+ } catch (ClassNotFoundException e) {
+ ServletException se = new ServletException(sm.getString(
+ "csrfPrevention.invalidRandomClass", randomClass), e);
+ throw se;
+ } catch (InstantiationException e) {
+ ServletException se = new ServletException(sm.getString(
+ "csrfPrevention.invalidRandomClass", randomClass), e);
+ throw se;
+ } catch (IllegalAccessException e) {
+ ServletException se = new ServletException(sm.getString(
+ "csrfPrevention.invalidRandomClass", randomClass), e);
+ throw se;
+ }
+ }
+
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
Added: trunk/java/org/apache/catalina/filters/ExpiresFilter.java
===================================================================
--- trunk/java/org/apache/catalina/filters/ExpiresFilter.java (rev 0)
+++ trunk/java/org/apache/catalina/filters/ExpiresFilter.java 2010-07-27 13:16:58 UTC (rev 1511)
@@ -0,0 +1,1569 @@
+/*
+ * Copyright 2008-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.filters;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+import java.util.regex.Pattern;
+
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+import org.jboss.logging.Logger;
+
+/**
+ * <p>
+ * ExpiresFilter is a Java Servlet API port of <a
+ * href="http://httpd.apache.org/docs/2.2/mod/mod_expires.html">Apache
+ * mod_expires</a> to add ' <tt>Expires</tt>' and '
+ * <tt>Cache-Control: max-age=</tt>' headers to HTTP response according to its '
+ * <tt>Content-Type</tt>'.
+ * </p>
+ *
+ * <p>
+ * Following documentation is inspired by <tt>mod_expires</tt> .
+ * </p>
+ * <h1>Summary</h1>
+ * <p>
+ * This filter controls the setting of the <tt>Expires</tt> HTTP header and the
+ * <tt>max-age</tt> directive of the <tt>Cache-Control</tt> HTTP header in
+ * server responses. The expiration date can set to be relative to either the
+ * time the source file was last modified, or to the time of the client access.
+ * </p>
+ * <p>
+ * These HTTP headers are an instruction to the client about the document's
+ * validity and persistence. If cached, the document may be fetched from the
+ * cache rather than from the source until this time has passed. After that, the
+ * cache copy is considered "expired" and invalid, and a new copy must
+ * be obtained from the source.
+ * </p>
+ * <p>
+ * To modify <tt>Cache-Control</tt> directives other than <tt>max-age</tt> (see
+ * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9" >RFC
+ * 2616 section 14.9</a>), you can use other servlet filters or <a
+ * href="http://httpd.apache.org/docs/2.2/mod/mod_headers.html" >Apache Httpd
+ * mod_headers</a> module.
+ * </p>
+ * <h1>Filter Configuration</h1><h2>Basic configuration to add '
+ * <tt>Expires</tt>' and ' <tt>Cache-Control: max-age=</tt>'
+ * headers to images, css and javascript</h2>
+ *
+ * <code><pre>
+ * <web-app ...>
+ * ...
+ * <filter>
+ * <filter-name>ExpiresFilter</filter-name>
+ * <filter-class>org.apache.catalina.filters.ExpiresFilter</filter-class>
+ * <init-param>
+ * <param-name>ExpiresByType image</param-name>
+ * <param-value>access plus 10 minutes</param-value>
+ * </init-param>
+ * <init-param>
+ * <param-name>ExpiresByType text/css</param-name>
+ * <param-value>access plus 10 minutes</param-value>
+ * </init-param>
+ * <init-param>
+ * <param-name>ExpiresByType text/javascript</param-name>
+ * <param-value>access plus 10 minutes</param-value>
+ * </init-param>
+ * </filter>
+ * ...
+ * <filter-mapping>
+ * <filter-name>ExpiresFilter</filter-name>
+ * <url-pattern>/*</url-pattern>
+ * <dispatcher>REQUEST</dispatcher>
+ * </filter-mapping>
+ * ...
+ * </web-app>
+ * </pre></code>
+ *
+ * <h2>Configuration Parameters</h2>
+ *
+ * <h3>
+ * <tt>ExpiresByType <content-type></tt></h3>
+ * <p>
+ * This directive defines the value of the <tt>Expires</tt> header and the
+ * <tt>max-age</tt> directive of the <tt>Cache-Control</tt> header generated for
+ * documents of the specified type (<i>e.g.</i>, <tt>text/html</tt>). The second
+ * argument sets the number of seconds that will be added to a base time to
+ * construct the expiration date. The <tt>Cache-Control: max-age</tt> is
+ * calculated by subtracting the request time from the expiration date and
+ * expressing the result in seconds.
+ * </p>
+ * <p>
+ * The base time is either the last modification time of the file, or the time
+ * of the client's access to the document. Which should be used is
+ * specified by the <tt><code></tt> field; <tt>M</tt> means that the
+ * file's last modification time should be used as the base time, and
+ * <tt>A</tt> means the client's access time should be used. The duration
+ * is expressed in seconds. <tt>A2592000</tt> stands for
+ * <tt>access plus 30 days</tt> in alternate syntax.
+ * </p>
+ * <p>
+ * The difference in effect is subtle. If <tt>M</tt> (<tt>modification</tt> in
+ * alternate syntax) is used, all current copies of the document in all caches
+ * will expire at the same time, which can be good for something like a weekly
+ * notice that's always found at the same URL. If <tt>A</tt> (
+ * <tt>access</tt> or <tt>now</tt> in alternate syntax) is used, the date of
+ * expiration is different for each client; this can be good for image files
+ * that don't change very often, particularly for a set of related
+ * documents that all refer to the same images (<i>i.e.</i>, the images will be
+ * accessed repeatedly within a relatively short timespan).
+ * </p>
+ * <p>
+ * <strong>Example:</strong>
+ * </p>
+ *
+ * <code><pre>
+ * <init-param>
+ * <param-name>ExpiresByType text/html</param-name><param-value>access plus 1 month 15 days 2 hours</param-value>
+ * </init-param>
+ *
+ * <init-param>
+ * <!-- 2592000 seconds = 30 days -->
+ * <param-name>ExpiresByType image/gif</param-name><param-value>A2592000</param-value>
+ * </init-param>
+ * </pre></code>
+ * <p>
+ * Note that this directive only has effect if <tt>ExpiresActive On</tt> has
+ * been specified. It overrides, for the specified MIME type <i>only</i>, any
+ * expiration date set by the <tt>ExpiresDefault</tt> directive.
+ * </p>
+ * <p>
+ * You can also specify the expiration time calculation using an alternate
+ * syntax, described earlier in this document.
+ * </p>
+ * <h3>
+ * <tt>ExpiresExcludedResponseStatusCodes</tt></h3>
+ * <p>
+ * This directive defines the http response status codes for which the
+ * <tt>ExpiresFilter</tt> will not generate expiration headers. By default, the
+ * <tt>304</tt> status code ("<tt>Not modified</tt>") is skipped. The
+ * value is a comma separated list of http status codes.
+ * </p>
+ * <p>
+ * This directive is useful to ease usage of <tt>ExpiresDefault</tt> directive.
+ * Indeed, the behavior of <tt>304 Not modified</tt> (which does specify a
+ * <tt>Content-Type</tt> header) combined with <tt>Expires</tt> and
+ * <tt>Cache-Control:max-age=</tt> headers can be unnecessarily tricky to
+ * understand.
+ * </p>
+ * <p>
+ * Configuration sample :
+ * </p>
+ *
+ * <code><pre>
+ * <init-param>
+ * <param-name>ExpiresExcludedResponseStatusCodes</param-name><param-value>302, 500, 503</param-value>
+ * </init-param>
+ * </pre></code>
+ *
+ * <h3>ExpiresDefault</h3>
+ * <p>
+ * This directive sets the default algorithm for calculating the expiration time
+ * for all documents in the affected realm. It can be overridden on a
+ * type-by-type basis by the <tt>ExpiresByType</tt> directive. See the
+ * description of that directive for details about the syntax of the argument,
+ * and the "alternate syntax" description as well.
+ * </p>
+ * <h1>Alternate Syntax</h1>
+ * <p>
+ * The <tt>ExpiresDefault</tt> and <tt>ExpiresByType</tt> directives can also be
+ * defined in a more readable syntax of the form:
+ * </p>
+ *
+ * <code><pre>
+ * <init-param>
+ * <param-name>ExpiresDefault</param-name><param-value><base> [plus] {<num> <type>}*</param-value>
+ * </init-param>
+ *
+ * <init-param>
+ * <param-name>ExpiresByType type/encoding</param-name><param-value><base> [plus] {<num> <type>}*</param-value>
+ * </init-param>
+ * </pre></code>
+ * <p>
+ * where <tt><base></tt> is one of:
+ * <ul>
+ * <li><tt>access</tt></li>
+ * <li><tt>now</tt> (equivalent to '<tt>access</tt>')</li>
+ * <li><tt>modification</tt></li>
+ * </ul>
+ * </p>
+ * <p>
+ * The <tt>plus</tt> keyword is optional. <tt><num></tt> should be an
+ * integer value (acceptable to <tt>Integer.parseInt()</tt>), and
+ * <tt><type></tt> is one of:
+ * <ul>
+ * <li><tt>years</tt></li>
+ * <li><tt>months</tt></li>
+ * <li><tt>weeks</tt></li>
+ * <li><tt>days</tt></li>
+ * <li><tt>hours</tt></li>
+ * <li><tt>minutes</tt></li>
+ * <li><tt>seconds</tt></li>
+ * </ul>
+ * For example, any of the following directives can be used to make documents
+ * expire 1 month after being accessed, by default:
+ * </p>
+ *
+ * <code><pre>
+ * <init-param>
+ * <param-name>ExpiresDefault</param-name><param-value>access plus 1 month</param-value>
+ * </init-param>
+ *
+ * <init-param>
+ * <param-name>ExpiresDefault</param-name><param-value>access plus 4 weeks</param-value>
+ * </init-param>
+ *
+ * <init-param>
+ * <param-name>ExpiresDefault</param-name><param-value>access plus 30 days</param-value>
+ * </init-param>
+ * </pre></code>
+ * <p>
+ * The expiry time can be fine-tuned by adding several '
+ * <tt><num> <type></tt>' clauses:
+ * </p>
+ *
+ * <code><pre>
+ * <init-param>
+ * <param-name>ExpiresByType text/html</param-name><param-value>access plus 1 month 15 days 2 hours</param-value>
+ * </init-param>
+ *
+ * <init-param>
+ * <param-name>ExpiresByType image/gif</param-name><param-value>modification plus 5 hours 3 minutes</param-value>
+ * </init-param>
+ * </pre></code>
+ * <p>
+ * Note that if you use a modification date based setting, the <tt>Expires</tt>
+ * header will <strong>not</strong> be added to content that does not come from
+ * a file on disk. This is due to the fact that there is no modification time
+ * for such content.
+ * </p>
+ * <h1>Expiration headers generation eligibility</h1>
+ * <p>
+ * A response is eligible to be enriched by <tt>ExpiresFilter</tt> if :
+ * <ol>
+ * <li>no expiration header is defined (<tt>Expires</tt> header or the
+ * <tt>max-age</tt> directive of the <tt>Cache-Control</tt> header),</li>
+ * <li>the response status code is not excluded by the directive
+ * <tt>ExpiresExcludedResponseStatusCodes</tt>,</li>
+ * <li>the <tt>Content-Type</tt> of the response matches one of the types
+ * defined the in <tt>ExpiresByType</tt> directives or the
+ * <tt>ExpiresDefault</tt> directive is defined.</li>
+ * </ol>
+ * </p>
+ * <p>
+ * Note :
+ * <ul>
+ * <li>If <tt>Cache-Control</tt> header contains other directives than
+ * <tt>max-age</tt>, they are concatenated with the <tt>max-age</tt> directive
+ * that is added by the <tt>ExpiresFilter</tt>.</li>
+ * </ul>
+ * </p>
+ * <h1>Expiration configuration selection</h1>
+ * <p>
+ * The expiration configuration if elected according to the following algorithm:
+ * <ol>
+ * <li><tt>ExpiresByType</tt> matching the exact content-type returned by
+ * <tt>HttpServletResponse.getContentType()</tt> possibly including the charset
+ * (e.g. '<tt>text/xml;charset=UTF-8</tt>'),</li>
+ * <li><tt>ExpiresByType</tt> matching the content-type without the charset if
+ * <tt>HttpServletResponse.getContentType()</tt> contains a charset (e.g. '
+ * <tt>text/xml;charset=UTF-8</tt>' -> '<tt>text/xml</tt>'),</li>
+ * <li><tt>ExpiresByType</tt> matching the major type (e.g. substring before
+ * '<tt>/</tt>') of <tt>HttpServletResponse.getContentType()</tt>
+ * (e.g. '<tt>text/xml;charset=UTF-8</tt>' -> '<tt>text</tt>
+ * '),</li>
+ * <li><tt>ExpiresDefault</tt></li>
+ * </ol>
+ * </p>
+ * <h1>Implementation Details</h1><h2>When to write the expiration headers ?</h2>
+ * <p>
+ * The <tt>ExpiresFilter</tt> traps the 'on before write response
+ * body' event to decide whether it should generate expiration headers or
+ * not.
+ * </p>
+ * <p>
+ * To trap the 'before write response body' event, the
+ * <tt>ExpiresFilter</tt> wraps the http servlet response's writer and
+ * outputStream to intercept calls to the methods <tt>write()</tt>,
+ * <tt>print()</tt>, <tt>close()</tt> and <tt>flush()</tt>. For empty response
+ * body (e.g. empty files), the <tt>write()</tt>, <tt>print()</tt>,
+ * <tt>close()</tt> and <tt>flush()</tt> methods are not called; to handle this
+ * case, the <tt>ExpiresFilter</tt>, at the end of its <tt>doFilter()</tt>
+ * method, manually triggers the <tt>onBeforeWriteResponseBody()</tt> method.
+ * </p>
+ * <h2>Configuration syntax</h2>
+ * <p>
+ * The <tt>ExpiresFilter</tt> supports the same configuration syntax as Apache
+ * Httpd mod_expires.
+ * </p>
+ * <p>
+ * A challenge has been to choose the name of the <tt><param-name></tt>
+ * associated with <tt>ExpiresByType</tt> in the <tt><filter></tt>
+ * declaration. Indeed, Several <tt>ExpiresByType</tt> directives can be
+ * declared when <tt>web.xml</tt> syntax does not allow to declare several
+ * <tt><init-param></tt> with the same name.
+ * </p>
+ * <p>
+ * The workaround has been to declare the content type in the
+ * <tt><param-name></tt> rather than in the <tt><param-value></tt>.
+ * </p>
+ * <h2>Designed for extension : the open/close principle</h2>
+ * <p>
+ * The <tt>ExpiresFilter</tt> has been designed for extension following the
+ * open/close principle.
+ * </p>
+ * <p>
+ * Key methods to override for extension are :
+ * <ul>
+ * <li>
+ * {@link #isEligibleToExpirationHeaderGeneration(HttpServletRequest, XHttpServletResponse)}
+ * </li>
+ * <li>
+ * {@link #getExpirationDate(HttpServletRequest, XHttpServletResponse)}</li>
+ * </ul>
+ * </p>
+ * <h1>Troubleshooting</h1>
+ * <p>
+ * To troubleshoot, enable logging on the
+ * <tt>org.apache.catalina.filters.ExpiresFilter</tt>.
+ * </p>
+ * <p>
+ * Extract of logging.properties
+ * </p>
+ *
+ * <code><pre>
+ * org.apache.catalina.filters.ExpiresFilter.level = FINE
+ * </pre></code>
+ * <p>
+ * Sample of initialization log message :
+ * </p>
+ *
+ * <code><pre>
+ * Mar 26, 2010 2:01:41 PM org.apache.catalina.filters.ExpiresFilter init
+ * FINE: Filter initialized with configuration ExpiresFilter[
+ * excludedResponseStatusCode=[304],
+ * default=null,
+ * byType={
+ * image=ExpiresConfiguration[startingPoint=ACCESS_TIME, duration=[10 MINUTE]],
+ * text/css=ExpiresConfiguration[startingPoint=ACCESS_TIME, duration=[10 MINUTE]],
+ * text/javascript=ExpiresConfiguration[startingPoint=ACCESS_TIME, duration=[10 MINUTE]]}]
+ * </pre></code>
+ * <p>
+ * Sample of per-request log message where <tt>ExpiresFilter</tt> adds an
+ * expiration date
+ * </p>
+ *
+ * <code><pre>
+ * Mar 26, 2010 2:09:47 PM org.apache.catalina.filters.ExpiresFilter onBeforeWriteResponseBody
+ * FINE: Request "/tomcat.gif" with response status "200" content-type "image/gif", set expiration date 3/26/10 2:19 PM
+ * </pre></code>
+ * <p>
+ * Sample of per-request log message where <tt>ExpiresFilter</tt> does not add
+ * an expiration date
+ * </p>
+ *
+ * <code><pre>
+ * Mar 26, 2010 2:10:27 PM org.apache.catalina.filters.ExpiresFilter onBeforeWriteResponseBody
+ * FINE: Request "/docs/config/manager.html" with response status "200" content-type "text/html", no expiration configured
+ * </pre></code>
+ *
+ */
+public class ExpiresFilter extends FilterBase {
+
+ /**
+ * Duration composed of an {@link #amount} and a {@link #unit}
+ */
+ protected static class Duration {
+
+ public static Duration minutes(int amount) {
+ return new Duration(amount, DurationUnit.MINUTE);
+ }
+
+ public static Duration seconds(int amount) {
+ return new Duration(amount, DurationUnit.SECOND);
+ }
+
+ final protected int amount;
+
+ final protected DurationUnit unit;
+
+ public Duration(int amount, DurationUnit unit) {
+ super();
+ this.amount = amount;
+ this.unit = unit;
+ }
+
+ public int getAmount() {
+ return amount;
+ }
+
+ public DurationUnit getUnit() {
+ return unit;
+ }
+
+ @Override
+ public String toString() {
+ return amount + " " + unit;
+ }
+ }
+
+ /**
+ * Duration unit
+ */
+ protected enum DurationUnit {
+ DAY(Calendar.DAY_OF_YEAR), HOUR(Calendar.HOUR), MINUTE(Calendar.MINUTE), MONTH(
+ Calendar.MONTH), SECOND(Calendar.SECOND), WEEK(
+ Calendar.WEEK_OF_YEAR), YEAR(Calendar.YEAR);
+ private final int calendardField;
+
+ private DurationUnit(int calendardField) {
+ this.calendardField = calendardField;
+ }
+
+ public int getCalendardField() {
+ return calendardField;
+ }
+
+ }
+
+ /**
+ * <p>
+ * Main piece of configuration of the filter.
+ * </p>
+ * <p>
+ * Can be expressed like '<tt>access plus 1 month 15 days 2 hours</tt>'.
+ * </p>
+ */
+ protected static class ExpiresConfiguration {
+ /**
+ * List of duration elements.
+ */
+ private List<Duration> durations;
+
+ /**
+ * Starting point of the elaspse to set in the response.
+ */
+ private StartingPoint startingPoint;
+
+ public ExpiresConfiguration(StartingPoint startingPoint,
+ Duration... durations) {
+ this(startingPoint, Arrays.asList(durations));
+ }
+
+ public ExpiresConfiguration(StartingPoint startingPoint,
+ List<Duration> durations) {
+ super();
+ this.startingPoint = startingPoint;
+ this.durations = durations;
+ }
+
+ public List<Duration> getDurations() {
+ return durations;
+ }
+
+ public StartingPoint getStartingPoint() {
+ return startingPoint;
+ }
+
+ @Override
+ public String toString() {
+ return "ExpiresConfiguration[startingPoint=" + startingPoint +
+ ", duration=" + durations + "]";
+ }
+ }
+
+ /**
+ * Expiration configuration starting point. Either the time the
+ * html-page/servlet-response was served ({@link StartingPoint#ACCESS_TIME})
+ * or the last time the html-page/servlet-response was modified (
+ * {@link StartingPoint#LAST_MODIFICATION_TIME}).
+ */
+ protected enum StartingPoint {
+ ACCESS_TIME, LAST_MODIFICATION_TIME
+ }
+
+ /**
+ * <p>
+ * Wrapping extension of the {@link HttpServletResponse} to yrap the
+ * "Start Write Response Body" event.
+ * </p>
+ * <p>
+ * For performance optimization : this extended response holds the
+ * {@link #lastModifiedHeader} and {@link #cacheControlHeader} values access
+ * to the slow {@link #getHeader(String)} and to spare the <tt>string</tt>
+ * to <tt>date</tt> to <tt>long</tt> conversion.
+ * </p>
+ */
+ public class XHttpServletResponse extends HttpServletResponseWrapper {
+
+ /**
+ * Value of the <tt>Cache-Control/tt> http response header if it has
+ * been set.
+ */
+ private String cacheControlHeader;
+
+ /**
+ * Value of the <tt>Last-Modified</tt> http response header if it has
+ * been set.
+ */
+ private long lastModifiedHeader;
+
+ private boolean lastModifiedHeaderSet;
+
+ private PrintWriter printWriter;
+
+ private HttpServletRequest request;
+
+ private ServletOutputStream servletOutputStream;
+
+ /**
+ * Indicates whether calls to write methods (<tt>write(...)</tt>,
+ * <tt>print(...)</tt>, etc) of the response body have been called or
+ * not.
+ */
+ private boolean writeResponseBodyStarted;
+
+ public XHttpServletResponse(HttpServletRequest request,
+ HttpServletResponse response) {
+ super(response);
+ this.request = request;
+ }
+
+ @Override
+ public void addDateHeader(String name, long date) {
+ super.addDateHeader(name, date);
+ if (!lastModifiedHeaderSet) {
+ this.lastModifiedHeader = date;
+ this.lastModifiedHeaderSet = true;
+ }
+ }
+
+ @Override
+ public void addHeader(String name, String value) {
+ super.addHeader(name, value);
+ if (HEADER_CACHE_CONTROL.equalsIgnoreCase(name) &&
+ cacheControlHeader == null) {
+ cacheControlHeader = value;
+ }
+ }
+
+ public String getCacheControlHeader() {
+ return cacheControlHeader;
+ }
+
+ public long getLastModifiedHeader() {
+ return lastModifiedHeader;
+ }
+
+ @Override
+ public ServletOutputStream getOutputStream() throws IOException {
+ if (servletOutputStream == null) {
+ servletOutputStream = new XServletOutputStream(
+ super.getOutputStream(), request, this);
+ }
+ return servletOutputStream;
+ }
+
+ @Override
+ public PrintWriter getWriter() throws IOException {
+ if (printWriter == null) {
+ printWriter = new XPrintWriter(super.getWriter(), request, this);
+ }
+ return printWriter;
+ }
+
+ public boolean isLastModifiedHeaderSet() {
+ return lastModifiedHeaderSet;
+ }
+
+ public boolean isWriteResponseBodyStarted() {
+ return writeResponseBodyStarted;
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ this.lastModifiedHeader = 0;
+ this.lastModifiedHeaderSet = false;
+ this.cacheControlHeader = null;
+ }
+
+ @Override
+ public void setDateHeader(String name, long date) {
+ super.setDateHeader(name, date);
+ if (HEADER_LAST_MODIFIED.equalsIgnoreCase(name)) {
+ this.lastModifiedHeader = date;
+ this.lastModifiedHeaderSet = true;
+ }
+ }
+
+ @Override
+ public void setHeader(String name, String value) {
+ super.setHeader(name, value);
+ if (HEADER_CACHE_CONTROL.equalsIgnoreCase(name)) {
+ this.cacheControlHeader = value;
+ }
+ }
+
+ public void setWriteResponseBodyStarted(boolean writeResponseBodyStarted) {
+ this.writeResponseBodyStarted = writeResponseBodyStarted;
+ }
+ }
+
+ /**
+ * Wrapping extension of {@link PrintWriter} to trap the
+ * "Start Write Response Body" event.
+ */
+ public class XPrintWriter extends PrintWriter {
+ private PrintWriter out;
+
+ private HttpServletRequest request;
+
+ private XHttpServletResponse response;
+
+ public XPrintWriter(PrintWriter out, HttpServletRequest request,
+ XHttpServletResponse response) {
+ super(out);
+ this.out = out;
+ this.request = request;
+ this.response = response;
+ }
+
+ @Override
+ public PrintWriter append(char c) {
+ fireBeforeWriteResponseBodyEvent();
+ return out.append(c);
+ }
+
+ @Override
+ public PrintWriter append(CharSequence csq) {
+ fireBeforeWriteResponseBodyEvent();
+ return out.append(csq);
+ }
+
+ @Override
+ public PrintWriter append(CharSequence csq, int start, int end) {
+ fireBeforeWriteResponseBodyEvent();
+ return out.append(csq, start, end);
+ }
+
+ @Override
+ public void close() {
+ fireBeforeWriteResponseBodyEvent();
+ out.close();
+ }
+
+ private void fireBeforeWriteResponseBodyEvent() {
+ if (!this.response.isWriteResponseBodyStarted()) {
+ this.response.setWriteResponseBodyStarted(true);
+ onBeforeWriteResponseBody(request, response);
+ }
+ }
+
+ @Override
+ public void flush() {
+ fireBeforeWriteResponseBodyEvent();
+ out.flush();
+ }
+
+ @Override
+ public void print(boolean b) {
+ fireBeforeWriteResponseBodyEvent();
+ out.print(b);
+ }
+
+ @Override
+ public void print(char c) {
+ fireBeforeWriteResponseBodyEvent();
+ out.print(c);
+ }
+
+ @Override
+ public void print(char[] s) {
+ fireBeforeWriteResponseBodyEvent();
+ out.print(s);
+ }
+
+ @Override
+ public void print(double d) {
+ fireBeforeWriteResponseBodyEvent();
+ out.print(d);
+ }
+
+ @Override
+ public void print(float f) {
+ fireBeforeWriteResponseBodyEvent();
+ out.print(f);
+ }
+
+ @Override
+ public void print(int i) {
+ fireBeforeWriteResponseBodyEvent();
+ out.print(i);
+ }
+
+ @Override
+ public void print(long l) {
+ fireBeforeWriteResponseBodyEvent();
+ out.print(l);
+ }
+
+ @Override
+ public void print(Object obj) {
+ fireBeforeWriteResponseBodyEvent();
+ out.print(obj);
+ }
+
+ @Override
+ public void print(String s) {
+ fireBeforeWriteResponseBodyEvent();
+ out.print(s);
+ }
+
+ @Override
+ public PrintWriter printf(Locale l, String format, Object... args) {
+ fireBeforeWriteResponseBodyEvent();
+ return out.printf(l, format, args);
+ }
+
+ @Override
+ public PrintWriter printf(String format, Object... args) {
+ fireBeforeWriteResponseBodyEvent();
+ return out.printf(format, args);
+ }
+
+ @Override
+ public void println() {
+ fireBeforeWriteResponseBodyEvent();
+ out.println();
+ }
+
+ @Override
+ public void println(boolean x) {
+ fireBeforeWriteResponseBodyEvent();
+ out.println(x);
+ }
+
+ @Override
+ public void println(char x) {
+ fireBeforeWriteResponseBodyEvent();
+ out.println(x);
+ }
+
+ @Override
+ public void println(char[] x) {
+ fireBeforeWriteResponseBodyEvent();
+ out.println(x);
+ }
+
+ @Override
+ public void println(double x) {
+ fireBeforeWriteResponseBodyEvent();
+ out.println(x);
+ }
+
+ @Override
+ public void println(float x) {
+ fireBeforeWriteResponseBodyEvent();
+ out.println(x);
+ }
+
+ @Override
+ public void println(int x) {
+ fireBeforeWriteResponseBodyEvent();
+ out.println(x);
+ }
+
+ @Override
+ public void println(long x) {
+ fireBeforeWriteResponseBodyEvent();
+ out.println(x);
+ }
+
+ @Override
+ public void println(Object x) {
+ fireBeforeWriteResponseBodyEvent();
+ out.println(x);
+ }
+
+ @Override
+ public void println(String x) {
+ fireBeforeWriteResponseBodyEvent();
+ out.println(x);
+ }
+
+ @Override
+ public void write(char[] buf) {
+ fireBeforeWriteResponseBodyEvent();
+ out.write(buf);
+ }
+
+ @Override
+ public void write(char[] buf, int off, int len) {
+ fireBeforeWriteResponseBodyEvent();
+ out.write(buf, off, len);
+ }
+
+ @Override
+ public void write(int c) {
+ fireBeforeWriteResponseBodyEvent();
+ out.write(c);
+ }
+
+ @Override
+ public void write(String s) {
+ fireBeforeWriteResponseBodyEvent();
+ out.write(s);
+ }
+
+ @Override
+ public void write(String s, int off, int len) {
+ fireBeforeWriteResponseBodyEvent();
+ out.write(s, off, len);
+ }
+
+ }
+
+ /**
+ * Wrapping extension of {@link ServletOutputStream} to trap the
+ * "Start Write Response Body" event.
+ */
+ public class XServletOutputStream extends ServletOutputStream {
+
+ private HttpServletRequest request;
+
+ private XHttpServletResponse response;
+
+ private ServletOutputStream servletOutputStream;
+
+ public XServletOutputStream(ServletOutputStream servletOutputStream,
+ HttpServletRequest request, XHttpServletResponse response) {
+ super();
+ this.servletOutputStream = servletOutputStream;
+ this.response = response;
+ this.request = request;
+ }
+
+ @Override
+ public void close() throws IOException {
+ fireOnBeforeWriteResponseBodyEvent();
+ servletOutputStream.close();
+ }
+
+ private void fireOnBeforeWriteResponseBodyEvent() {
+ if (!this.response.isWriteResponseBodyStarted()) {
+ this.response.setWriteResponseBodyStarted(true);
+ onBeforeWriteResponseBody(request, response);
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ fireOnBeforeWriteResponseBodyEvent();
+ servletOutputStream.flush();
+ }
+
+ @Override
+ public void print(boolean b) throws IOException {
+ fireOnBeforeWriteResponseBodyEvent();
+ servletOutputStream.print(b);
+ }
+
+ @Override
+ public void print(char c) throws IOException {
+ fireOnBeforeWriteResponseBodyEvent();
+ servletOutputStream.print(c);
+ }
+
+ @Override
+ public void print(double d) throws IOException {
+ fireOnBeforeWriteResponseBodyEvent();
+ servletOutputStream.print(d);
+ }
+
+ @Override
+ public void print(float f) throws IOException {
+ fireOnBeforeWriteResponseBodyEvent();
+ servletOutputStream.print(f);
+ }
+
+ @Override
+ public void print(int i) throws IOException {
+ fireOnBeforeWriteResponseBodyEvent();
+ servletOutputStream.print(i);
+ }
+
+ @Override
+ public void print(long l) throws IOException {
+ fireOnBeforeWriteResponseBodyEvent();
+ servletOutputStream.print(l);
+ }
+
+ @Override
+ public void print(String s) throws IOException {
+ fireOnBeforeWriteResponseBodyEvent();
+ servletOutputStream.print(s);
+ }
+
+ @Override
+ public void println() throws IOException {
+ fireOnBeforeWriteResponseBodyEvent();
+ servletOutputStream.println();
+ }
+
+ @Override
+ public void println(boolean b) throws IOException {
+ fireOnBeforeWriteResponseBodyEvent();
+ servletOutputStream.println(b);
+ }
+
+ @Override
+ public void println(char c) throws IOException {
+ fireOnBeforeWriteResponseBodyEvent();
+ servletOutputStream.println(c);
+ }
+
+ @Override
+ public void println(double d) throws IOException {
+ fireOnBeforeWriteResponseBodyEvent();
+ servletOutputStream.println(d);
+ }
+
+ @Override
+ public void println(float f) throws IOException {
+ fireOnBeforeWriteResponseBodyEvent();
+ servletOutputStream.println(f);
+ }
+
+ @Override
+ public void println(int i) throws IOException {
+ fireOnBeforeWriteResponseBodyEvent();
+ servletOutputStream.println(i);
+ }
+
+ @Override
+ public void println(long l) throws IOException {
+ fireOnBeforeWriteResponseBodyEvent();
+ servletOutputStream.println(l);
+ }
+
+ @Override
+ public void println(String s) throws IOException {
+ fireOnBeforeWriteResponseBodyEvent();
+ servletOutputStream.println(s);
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ fireOnBeforeWriteResponseBodyEvent();
+ servletOutputStream.write(b);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ fireOnBeforeWriteResponseBodyEvent();
+ servletOutputStream.write(b, off, len);
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ fireOnBeforeWriteResponseBodyEvent();
+ servletOutputStream.write(b);
+ }
+
+ }
+
+ /**
+ * {@link Pattern} for a comma delimited string that support whitespace
+ * characters
+ */
+ private static final Pattern commaSeparatedValuesPattern = Pattern.compile("\\s*,\\s*");
+
+ private static final String HEADER_CACHE_CONTROL = "Cache-Control";
+
+ private static final String HEADER_EXPIRES = "Expires";
+
+ private static final String HEADER_LAST_MODIFIED = "Last-Modified";
+
+ private static Logger log = Logger.getLogger(ExpiresFilter.class);
+
+ private static final String PARAMETER_EXPIRES_BY_TYPE = "ExpiresByType";
+
+ private static final String PARAMETER_EXPIRES_DEFAULT = "ExpiresDefault";
+
+ private static final String PARAMETER_EXPIRES_EXCLUDED_RESPONSE_STATUS_CODES = "ExpiresExcludedResponseStatusCodes";
+
+ /**
+ * Convert a comma delimited list of numbers into an <tt>int[]</tt>.
+ *
+ * @param commaDelimitedInts
+ * can be <code>null</code>
+ * @return never <code>null</code> array
+ */
+ protected static int[] commaDelimitedListToIntArray(
+ String commaDelimitedInts) {
+ String[] intsAsStrings = commaDelimitedListToStringArray(commaDelimitedInts);
+ int[] ints = new int[intsAsStrings.length];
+ for (int i = 0; i < intsAsStrings.length; i++) {
+ String intAsString = intsAsStrings[i];
+ try {
+ ints[i] = Integer.parseInt(intAsString);
+ } catch (NumberFormatException e) {
+ throw new RuntimeException("Exception parsing number '" + i +
+ "' (zero based) of comma delimited list '" +
+ commaDelimitedInts + "'");
+ }
+ }
+ return ints;
+ }
+
+ /**
+ * Convert a given comma delimited list of strings into an array of String
+ *
+ * @return array of patterns (non <code>null</code>)
+ */
+ protected static String[] commaDelimitedListToStringArray(
+ String commaDelimitedStrings) {
+ return (commaDelimitedStrings == null || commaDelimitedStrings.length() == 0) ? new String[0]
+ : commaSeparatedValuesPattern.split(commaDelimitedStrings);
+ }
+
+ /**
+ * Return <code>true</code> if the given <code>str</code> contains the given
+ * <code>searchStr</code>.
+ */
+ protected static boolean contains(String str, String searchStr) {
+ if (str == null || searchStr == null) {
+ return false;
+ }
+ return str.indexOf(searchStr) >= 0;
+ }
+
+ /**
+ * Convert an array of ints into a comma delimited string
+ */
+ protected static String intsToCommaDelimitedString(int[] ints) {
+ if (ints == null) {
+ return "";
+ }
+
+ StringBuilder result = new StringBuilder();
+
+ for (int i = 0; i < ints.length; i++) {
+ result.append(ints[i]);
+ if (i < (ints.length - 1)) {
+ result.append(", ");
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * Return <code>true</code> if the given <code>str</code> is
+ * <code>null</code> or has a zero characters length.
+ */
+ protected static boolean isEmpty(String str) {
+ return str == null || str.length() == 0;
+ }
+
+ /**
+ * Return <code>true</code> if the given <code>str</code> has at least one
+ * character (can be a withespace).
+ */
+ protected static boolean isNotEmpty(String str) {
+ return !isEmpty(str);
+ }
+
+ /**
+ * Return <code>true</code> if the given <code>string</code> starts with the
+ * given <code>prefix</code> ignoring case.
+ *
+ * @param string
+ * can be <code>null</code>
+ * @param prefix
+ * can be <code>null</code>
+ */
+ protected static boolean startsWithIgnoreCase(String string, String prefix) {
+ if (string == null || prefix == null) {
+ return string == null && prefix == null;
+ }
+ if (prefix.length() > string.length()) {
+ return false;
+ }
+
+ return string.regionMatches(true, 0, prefix, 0, prefix.length());
+ }
+
+ /**
+ * Return the subset of the given <code>str</code> that is before the first
+ * occurence of the given <code>separator</code>. Return <code>null</code>
+ * if the given <code>str</code> or the given <code>separator</code> is
+ * null. Return and empty string if the <code>separator</code> is empty.
+ *
+ * @param str
+ * can be <code>null</code>
+ * @param separator
+ * can be <code>null</code>
+ * @return
+ */
+ protected static String substringBefore(String str, String separator) {
+ if (str == null || str.isEmpty() || separator == null) {
+ return null;
+ }
+
+ if (separator.isEmpty()) {
+ return "";
+ }
+
+ int separatorIndex = str.indexOf(separator);
+ if (separatorIndex == -1) {
+ return str;
+ }
+ return str.substring(0, separatorIndex);
+ }
+
+ /**
+ * Default Expires configuration.
+ */
+ private ExpiresConfiguration defaultExpiresConfiguration;
+
+ /**
+ * list of response status code for which the {@link ExpiresFilter} will not
+ * generate expiration headers.
+ */
+ private int[] excludedResponseStatusCodes = new int[] { HttpServletResponse.SC_NOT_MODIFIED };
+
+ /**
+ * Expires configuration by content type. Visible for test.
+ */
+ private Map<String, ExpiresConfiguration> expiresConfigurationByContentType = new LinkedHashMap<String, ExpiresConfiguration>();
+
+ public void doFilter(ServletRequest request, ServletResponse response,
+ FilterChain chain) throws IOException, ServletException {
+ if (request instanceof HttpServletRequest &&
+ response instanceof HttpServletResponse) {
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+ HttpServletResponse httpResponse = (HttpServletResponse) response;
+
+ if (response.isCommitted()) {
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString(
+ "expiresFilter.responseAlreadyCommited",
+ httpRequest.getRequestURL()));
+ }
+ chain.doFilter(request, response);
+ } else {
+ XHttpServletResponse xResponse = new XHttpServletResponse(
+ httpRequest, httpResponse);
+ chain.doFilter(request, xResponse);
+ if (!xResponse.isWriteResponseBodyStarted()) {
+ // Empty response, manually trigger
+ // onBeforeWriteResponseBody()
+ onBeforeWriteResponseBody(httpRequest, xResponse);
+ }
+ }
+ } else {
+ chain.doFilter(request, response);
+ }
+ }
+
+ public ExpiresConfiguration getDefaultExpiresConfiguration() {
+ return defaultExpiresConfiguration;
+ }
+
+ public String getExcludedResponseStatusCodes() {
+ return intsToCommaDelimitedString(excludedResponseStatusCodes);
+ }
+
+ public int[] getExcludedResponseStatusCodesAsInts() {
+ return excludedResponseStatusCodes;
+ }
+
+ /**
+ * <p>
+ * Returns the expiration date of the given {@link XHttpServletResponse} or
+ * <code>null</code> if no expiration date has been configured for the
+ * declared content type.
+ * </p>
+ * <p>
+ * <code>protected</code> for extension.
+ * </p>
+ *
+ * @see HttpServletResponse#getContentType()
+ */
+ protected Date getExpirationDate(XHttpServletResponse response) {
+ String contentType = response.getContentType();
+
+ // lookup exact content-type match (e.g.
+ // "text/html; charset=iso-8859-1")
+ ExpiresConfiguration configuration = expiresConfigurationByContentType.get(contentType);
+ if (configuration != null) {
+ Date result = getExpirationDate(configuration, response);
+ return result;
+ }
+
+ if (contains(contentType, ";")) {
+ // lookup content-type without charset match (e.g. "text/html")
+ String contentTypeWithoutCharset = substringBefore(contentType, ";").trim();
+ configuration = expiresConfigurationByContentType.get(contentTypeWithoutCharset);
+
+ if (configuration != null) {
+ Date result = getExpirationDate(configuration, response);
+ return result;
+ }
+ }
+
+ if (contains(contentType, "/")) {
+ // lookup major type match (e.g. "text")
+ String majorType = substringBefore(contentType, "/");
+ configuration = expiresConfigurationByContentType.get(majorType);
+ if (configuration != null) {
+ Date result = getExpirationDate(configuration, response);
+ return result;
+ }
+ }
+
+ if (defaultExpiresConfiguration != null) {
+ Date result = getExpirationDate(defaultExpiresConfiguration,
+ response);
+ return result;
+ }
+
+ return null;
+ }
+
+ /**
+ * <p>
+ * Returns the expiration date of the given {@link ExpiresConfiguration},
+ * {@link HttpServletRequest} and {@link XHttpServletResponse}.
+ * </p>
+ * <p>
+ * <code>protected</code> for extension.
+ * </p>
+ */
+ protected Date getExpirationDate(ExpiresConfiguration configuration,
+ XHttpServletResponse response) {
+ Calendar calendar;
+ switch (configuration.getStartingPoint()) {
+ case ACCESS_TIME:
+ calendar = Calendar.getInstance();
+ break;
+ case LAST_MODIFICATION_TIME:
+ if (response.isLastModifiedHeaderSet()) {
+ try {
+ long lastModified = response.getLastModifiedHeader();
+ calendar = Calendar.getInstance();
+ calendar.setTimeInMillis(lastModified);
+ } catch (NumberFormatException e) {
+ // default to now
+ calendar = Calendar.getInstance();
+ }
+ } else {
+ // Last-Modified header not found, use now
+ calendar = Calendar.getInstance();
+ }
+ break;
+ default:
+ throw new IllegalStateException(sm.getString(
+ "expiresFilter.unsupportedStartingPoint",
+ configuration.getStartingPoint()));
+ }
+ for (Duration duration : configuration.getDurations()) {
+ calendar.add(duration.getUnit().getCalendardField(),
+ duration.getAmount());
+ }
+
+ return calendar.getTime();
+ }
+
+ public Map<String, ExpiresConfiguration> getExpiresConfigurationByContentType() {
+ return expiresConfigurationByContentType;
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ for (Enumeration<String> names = filterConfig.getInitParameterNames(); names.hasMoreElements();) {
+ String name = names.nextElement();
+ String value = filterConfig.getInitParameter(name);
+
+ try {
+ if (name.startsWith(PARAMETER_EXPIRES_BY_TYPE)) {
+ String contentType = name.substring(
+ PARAMETER_EXPIRES_BY_TYPE.length()).trim();
+ ExpiresConfiguration expiresConfiguration = parseExpiresConfiguration(value);
+ this.expiresConfigurationByContentType.put(contentType,
+ expiresConfiguration);
+ } else if (name.equalsIgnoreCase(PARAMETER_EXPIRES_DEFAULT)) {
+ ExpiresConfiguration expiresConfiguration = parseExpiresConfiguration(value);
+ this.defaultExpiresConfiguration = expiresConfiguration;
+ } else if (name.equalsIgnoreCase(PARAMETER_EXPIRES_EXCLUDED_RESPONSE_STATUS_CODES)) {
+ this.excludedResponseStatusCodes = commaDelimitedListToIntArray(value);
+ } else {
+ log.warn(sm.getString(
+ "expiresFilter.unknownParameterIgnored", name,
+ value));
+ }
+ } catch (RuntimeException e) {
+ throw new ServletException(sm.getString(
+ "expiresFilter.exceptionProcessingParameter", name,
+ value), e);
+ }
+ }
+
+ log.debug(sm.getString("expiresFilter.filterInitialized",
+ this.toString()));
+ }
+
+ /**
+ *
+ * <p>
+ * <code>protected</code> for extension.
+ * </p>
+ */
+ protected boolean isEligibleToExpirationHeaderGeneration(
+ HttpServletRequest request, XHttpServletResponse response) {
+ boolean expirationHeaderHasBeenSet = response.containsHeader(HEADER_EXPIRES) ||
+ contains(response.getCacheControlHeader(), "max-age");
+ if (expirationHeaderHasBeenSet) {
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString(
+ "expiresFilter.expirationHeaderAlreadyDefined",
+ request.getRequestURI(),
+ Integer.valueOf(response.getStatus()),
+ response.getContentType()));
+ }
+ return false;
+ }
+
+ for (int skippedStatusCode : this.excludedResponseStatusCodes) {
+ if (response.getStatus() == skippedStatusCode) {
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("expiresFilter.skippedStatusCode",
+ request.getRequestURI(),
+ Integer.valueOf(response.getStatus()),
+ response.getContentType()));
+ }
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * <p>
+ * If no expiration header has been set by the servlet and an expiration has
+ * been defined in the {@link ExpiresFilter} configuration, sets the '
+ * <tt>Expires</tt>' header and the attribute '<tt>max-age</tt>' of the '
+ * <tt>Cache-Control</tt>' header.
+ * </p>
+ * <p>
+ * Must be called on the "Start Write Response Body" event.
+ * </p>
+ * <p>
+ * Invocations to <tt>Logger.debug(...)</tt> are guarded by
+ * {@link Logger#isDebugEnabled()} because
+ * {@link HttpServletRequest#getRequestURI()} and
+ * {@link HttpServletResponse#getContentType()} costs <tt>String</tt>
+ * objects instantiations (as of Tomcat 7).
+ * </p>
+ */
+ public void onBeforeWriteResponseBody(HttpServletRequest request,
+ XHttpServletResponse response) {
+
+ if (!isEligibleToExpirationHeaderGeneration(request, response)) {
+ return;
+ }
+
+ Date expirationDate = getExpirationDate(response);
+ if (expirationDate != null) {
+ String maxAgeDirective = "max-age=" +
+ ((expirationDate.getTime() - System.currentTimeMillis()) / 1000);
+
+ String cacheControlHeader = response.getCacheControlHeader();
+ String newCacheControlHeader = (cacheControlHeader == null) ? maxAgeDirective
+ : cacheControlHeader + ", " + maxAgeDirective;
+ response.setHeader(HEADER_CACHE_CONTROL, newCacheControlHeader);
+ response.setDateHeader(HEADER_EXPIRES, expirationDate.getTime());
+ }
+
+ }
+
+ /**
+ * Parse configuration lines like '
+ * <tt>access plus 1 month 15 days 2 hours</tt>' or '
+ * <tt>modification 1 day 2 hours 5 seconds</tt>'
+ *
+ * @param inputLine
+ */
+ protected ExpiresConfiguration parseExpiresConfiguration(String inputLine) {
+ String line = inputLine.trim();
+
+ StringTokenizer tokenizer = new StringTokenizer(line, " ");
+
+ String currentToken;
+
+ try {
+ currentToken = tokenizer.nextToken();
+ } catch (NoSuchElementException e) {
+ throw new IllegalStateException(sm.getString(
+ "expiresFilter.startingPointNotFound", line));
+ }
+
+ StartingPoint startingPoint;
+ if ("access".equalsIgnoreCase(currentToken) ||
+ "now".equalsIgnoreCase(currentToken)) {
+ startingPoint = StartingPoint.ACCESS_TIME;
+ } else if ("modification".equalsIgnoreCase(currentToken)) {
+ startingPoint = StartingPoint.LAST_MODIFICATION_TIME;
+ } else if (!tokenizer.hasMoreTokens() &&
+ startsWithIgnoreCase(currentToken, "a")) {
+ startingPoint = StartingPoint.ACCESS_TIME;
+ // trick : convert duration configuration from old to new style
+ tokenizer = new StringTokenizer(currentToken.substring(1) +
+ " seconds", " ");
+ } else if (!tokenizer.hasMoreTokens() &&
+ startsWithIgnoreCase(currentToken, "m")) {
+ startingPoint = StartingPoint.LAST_MODIFICATION_TIME;
+ // trick : convert duration configuration from old to new style
+ tokenizer = new StringTokenizer(currentToken.substring(1) +
+ " seconds", " ");
+ } else {
+ throw new IllegalStateException(sm.getString(
+ "expiresFilter.startingPointInvalid", currentToken, line));
+ }
+
+ try {
+ currentToken = tokenizer.nextToken();
+ } catch (NoSuchElementException e) {
+ throw new IllegalStateException(sm.getString(
+ "Duration not found in directive '{}'", line));
+ }
+
+ if ("plus".equalsIgnoreCase(currentToken)) {
+ // skip
+ try {
+ currentToken = tokenizer.nextToken();
+ } catch (NoSuchElementException e) {
+ throw new IllegalStateException(sm.getString(
+ "Duration not found in directive '{}'", line));
+ }
+ }
+
+ List<Duration> durations = new ArrayList<Duration>();
+
+ while (currentToken != null) {
+ int amount;
+ try {
+ amount = Integer.parseInt(currentToken);
+ } catch (NumberFormatException e) {
+ throw new IllegalStateException(sm.getString(
+ "Invalid duration (number) '{}' in directive '{}'",
+ currentToken, line));
+ }
+
+ try {
+ currentToken = tokenizer.nextToken();
+ } catch (NoSuchElementException e) {
+ throw new IllegalStateException(
+ sm.getString(
+ "Duration unit not found after amount {} in directive '{}'",
+ Integer.valueOf(amount), line));
+ }
+ DurationUnit durationUnit;
+ if ("years".equalsIgnoreCase(currentToken)) {
+ durationUnit = DurationUnit.YEAR;
+ } else if ("month".equalsIgnoreCase(currentToken) ||
+ "months".equalsIgnoreCase(currentToken)) {
+ durationUnit = DurationUnit.MONTH;
+ } else if ("week".equalsIgnoreCase(currentToken) ||
+ "weeks".equalsIgnoreCase(currentToken)) {
+ durationUnit = DurationUnit.WEEK;
+ } else if ("day".equalsIgnoreCase(currentToken) ||
+ "days".equalsIgnoreCase(currentToken)) {
+ durationUnit = DurationUnit.DAY;
+ } else if ("hour".equalsIgnoreCase(currentToken) ||
+ "hours".equalsIgnoreCase(currentToken)) {
+ durationUnit = DurationUnit.HOUR;
+ } else if ("minute".equalsIgnoreCase(currentToken) ||
+ "minutes".equalsIgnoreCase(currentToken)) {
+ durationUnit = DurationUnit.MINUTE;
+ } else if ("second".equalsIgnoreCase(currentToken) ||
+ "seconds".equalsIgnoreCase(currentToken)) {
+ durationUnit = DurationUnit.SECOND;
+ } else {
+ throw new IllegalStateException(
+ sm.getString(
+ "Invalid duration unit (years|months|weeks|days|hours|minutes|seconds) '{}' in directive '{}'",
+ currentToken, line));
+ }
+
+ Duration duration = new Duration(amount, durationUnit);
+ durations.add(duration);
+
+ if (tokenizer.hasMoreTokens()) {
+ currentToken = tokenizer.nextToken();
+ } else {
+ currentToken = null;
+ }
+ }
+
+ return new ExpiresConfiguration(startingPoint, durations);
+ }
+
+ public void setDefaultExpiresConfiguration(
+ ExpiresConfiguration defaultExpiresConfiguration) {
+ this.defaultExpiresConfiguration = defaultExpiresConfiguration;
+ }
+
+ public void setExcludedResponseStatusCodes(int[] excludedResponseStatusCodes) {
+ this.excludedResponseStatusCodes = excludedResponseStatusCodes;
+ }
+
+ public void setExpiresConfigurationByContentType(
+ Map<String, ExpiresConfiguration> expiresConfigurationByContentType) {
+ this.expiresConfigurationByContentType = expiresConfigurationByContentType;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "[excludedResponseStatusCode=[" +
+ intsToCommaDelimitedString(this.excludedResponseStatusCodes) +
+ "], default=" + this.defaultExpiresConfiguration + ", byType=" +
+ this.expiresConfigurationByContentType + "]";
+ }
+}
Modified: trunk/java/org/apache/catalina/filters/LocalStrings.properties
===================================================================
--- trunk/java/org/apache/catalina/filters/LocalStrings.properties 2010-07-21 12:14:34 UTC (rev 1510)
+++ trunk/java/org/apache/catalina/filters/LocalStrings.properties 2010-07-27 13:16:58 UTC (rev 1511)
@@ -16,3 +16,20 @@
filterbase.noSuchProperty=The property "{0}" is not defined for filters of type "{1}"
http.403=Access to the specified resource ({0}) has been forbidden.
+
+csrfPrevention.invalidRandomClass=Unable to create Random source using class [{0}]
+
+expiresFilter.noExpirationConfigured=Request "{0}" with response status "{1}" content-type "{2}", no expiration configured
+expiresFilter.setExpirationDate=Request "{0}" with response status "{1}" content-type "{2}", set expiration date {3}
+expiresFilter.startingPointNotFound=Starting point (access|now|modification|a<seconds>|m<seconds>) not found in directive "{0}"
+expiresFilter.startingPointInvalid=Invalid starting point (access|now|modification|a<seconds>|m<seconds>) "{0}" in directive "{1}"
+expiresFilter.responseAlreadyCommited=Request "{0}", can not apply ExpiresFilter on already committed response.
+expiresFilter.noExpirationConfiguredForContentType=No Expires configuration found for content-type "{0}"
+expiresFilter.useMatchingConfiguration=Use {0} matching "{1}" for content-type "{2}" returns {3}
+expiresFilter.useDefaultConfiguration=Use default {0} for content-type "{1}" returns {2}
+expiresFilter.unsupportedStartingPoint=Unsupported startingPoint "{0}"
+expiresFilter.unknownParameterIgnored=Unknown parameter "{0}" with value "{1}" is ignored !
+expiresFilter.exceptionProcessingParameter=Exception processing configuration parameter "{0}":"{1}"
+expiresFilter.filterInitialized=Filter initialized with configuration {0}
+expiresFilter.expirationHeaderAlreadyDefined=Request "{0}" with response status "{1}" content-type "{2}", expiration header already defined
+expiresFilter.skippedStatusCode=Request "{0}" with response status "{1}" content-type "{1}", skip expiration header generation for given status
Modified: trunk/webapps/docs/changelog.xml
===================================================================
--- trunk/webapps/docs/changelog.xml 2010-07-21 12:14:34 UTC (rev 1510)
+++ trunk/webapps/docs/changelog.xml 2010-07-27 13:16:58 UTC (rev 1511)
@@ -28,6 +28,12 @@
<fix>
<bug>49613</bug>: Improve getAttributeNames() performance when using SSL in some cases. (remm)
</fix>
+ <add>
+ mod_expires clone submitted by Cyrille Le Clerc. (markt)
+ </add>
+ <fix>
+ CSRF filter updates. (markt)
+ </fix>
</changelog>
</subsection>
<subsection name="Coyote">
13 years, 9 months
JBossWeb SVN: r1510 - trunk/java/org/apache/catalina/connector.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2010-07-21 08:14:34 -0400 (Wed, 21 Jul 2010)
New Revision: 1510
Modified:
trunk/java/org/apache/catalina/connector/Request.java
Log:
- Drop unused arbitrary value.
Modified: trunk/java/org/apache/catalina/connector/Request.java
===================================================================
--- trunk/java/org/apache/catalina/connector/Request.java 2010-07-20 14:17:23 UTC (rev 1509)
+++ trunk/java/org/apache/catalina/connector/Request.java 2010-07-21 12:14:34 UTC (rev 1510)
@@ -276,7 +276,7 @@
/**
* Async timeout.
*/
- protected long asyncTimeout = 600000L;
+ protected long asyncTimeout = -1;
/**
@@ -501,7 +501,7 @@
sslAttributes = false;
asyncContext = null;
- asyncTimeout = 300000;
+ asyncTimeout = -1;
canStartAsync = true;
asyncListeners.clear();
authType = null;
13 years, 9 months
JBossWeb SVN: r1509 - in trunk: java/org/apache/catalina/connector and 1 other directories.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2010-07-20 10:17:23 -0400 (Tue, 20 Jul 2010)
New Revision: 1509
Modified:
trunk/java/org/apache/catalina/authenticator/SSLAuthenticator.java
trunk/java/org/apache/catalina/connector/Request.java
trunk/webapps/docs/changelog.xml
Log:
- 49613: Improve SSL attributes access performance when repeatedly accessing them if some are null.
- Move certificate chain retrieval to the request.
Modified: trunk/java/org/apache/catalina/authenticator/SSLAuthenticator.java
===================================================================
--- trunk/java/org/apache/catalina/authenticator/SSLAuthenticator.java 2010-07-15 08:49:13 UTC (rev 1508)
+++ trunk/java/org/apache/catalina/authenticator/SSLAuthenticator.java 2010-07-20 14:17:23 UTC (rev 1509)
@@ -129,15 +129,8 @@
if (containerLog.isDebugEnabled())
containerLog.debug(" Looking up certificates");
- X509Certificate certs[] = (X509Certificate[])
- request.getAttribute(Globals.CERTIFICATES_ATTR);
+ X509Certificate certs[] = request.getCertificateChain();
if ((certs == null) || (certs.length < 1)) {
- request.getCoyoteRequest().action
- (ActionCode.ACTION_REQ_SSL_CERTIFICATE, null);
- certs = (X509Certificate[])
- request.getAttribute(Globals.CERTIFICATES_ATTR);
- }
- if ((certs == null) || (certs.length < 1)) {
if (containerLog.isDebugEnabled())
containerLog.debug(" No certificates included with this request");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
Modified: trunk/java/org/apache/catalina/connector/Request.java
===================================================================
--- trunk/java/org/apache/catalina/connector/Request.java 2010-07-15 08:49:13 UTC (rev 1508)
+++ trunk/java/org/apache/catalina/connector/Request.java 2010-07-20 14:17:23 UTC (rev 1509)
@@ -53,6 +53,7 @@
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
+import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
@@ -471,6 +472,12 @@
protected boolean canStartAsync = true;
+ /**
+ * Get SSL attributes.
+ */
+ protected boolean sslAttributes = false;
+
+
// --------------------------------------------------------- Public Methods
@@ -492,6 +499,7 @@
event = null;
}
+ sslAttributes = false;
asyncContext = null;
asyncTimeout = 300000;
canStartAsync = true;
@@ -1071,7 +1079,8 @@
attr = coyoteRequest.getAttribute(name);
if(attr != null)
return attr;
- if( isSSLAttribute(name) ) {
+ if( !sslAttributes && isSSLAttribute(name) ) {
+ sslAttributes = true;
coyoteRequest.action(ActionCode.ACTION_REQ_SSL_ATTRIBUTE,
coyoteRequest);
attr = coyoteRequest.getAttribute(Globals.CERTIFICATES_ATTR);
@@ -1095,6 +1104,20 @@
return attr;
}
+
+ public X509Certificate[] getCertificateChain() {
+ X509Certificate certs[] = (X509Certificate[]) getAttribute(Globals.CERTIFICATES_ATTR);
+ if ((certs == null) || (certs.length < 1)) {
+ coyoteRequest.action(ActionCode.ACTION_REQ_SSL_CERTIFICATE,
+ coyoteRequest);
+ certs = (X509Certificate[]) coyoteRequest.getAttribute(Globals.CERTIFICATES_ATTR);
+ if (certs != null) {
+ attributes.put(Globals.CERTIFICATES_ATTR, certs);
+ }
+ }
+ return certs;
+ }
+
/**
* Test if a given name is one of the special Servlet-spec SSL attributes.
Modified: trunk/webapps/docs/changelog.xml
===================================================================
--- trunk/webapps/docs/changelog.xml 2010-07-15 08:49:13 UTC (rev 1508)
+++ trunk/webapps/docs/changelog.xml 2010-07-20 14:17:23 UTC (rev 1509)
@@ -25,8 +25,21 @@
<fix>
Various CSRF filter updates. (markt)
</fix>
+ <fix>
+ <bug>49613</bug>: Improve getAttributeNames() performance when using SSL in some cases. (remm)
+ </fix>
</changelog>
</subsection>
+ <subsection name="Coyote">
+ <changelog>
+ <fix>
+ Fix NPE recycling buffered input filter and reduce memory use. (remm)
+ </fix>
+ <fix>
+ Skip out invalid headers when writing the response. (remm)
+ </fix>
+ </changelog>
+ </subsection>
</section>
<section name="JBoss Web 3.0.0.Beta6 (remm)">
13 years, 9 months
JBossWeb SVN: r1508 - branches/2.1.x.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2010-07-15 04:49:13 -0400 (Thu, 15 Jul 2010)
New Revision: 1508
Modified:
branches/2.1.x/dist.xml
Log:
- New repository location.
Modified: branches/2.1.x/dist.xml
===================================================================
--- branches/2.1.x/dist.xml 2010-07-14 09:02:39 UTC (rev 1507)
+++ branches/2.1.x/dist.xml 2010-07-15 08:49:13 UTC (rev 1508)
@@ -292,11 +292,9 @@
</fileset>
</copy>
- <!-- Maven repository configuration -->
- <property name="maven.repository.url" value="file:///somepath"/>
- <property name="maven.repository.id" value="repository.jboss.org"/>
- <!--<property name="maven.repository.url" value="dav:https://snapshots.jboss.org/maven2"/>
- <property name="maven.repository.id" value="snapshots.jboss.org"/>-->
+ <!-- Maven repository configuration -->
+ <property name="maven.repository.url" value="https://repository.jboss.org/nexus/service/local/staging/deploy/maven2"/>
+ <property name="maven.repository.id" value="jboss-releases-repository"/>
<!-- Linux/Unix execs -->
<exec dir="." executable="/bin/sh" os="Linux">
13 years, 10 months
JBossWeb SVN: r1507 - tags.
by jbossweb-commits@lists.jboss.org
Author: jfrederic.clere(a)jboss.com
Date: 2010-07-14 05:02:39 -0400 (Wed, 14 Jul 2010)
New Revision: 1507
Added:
tags/JBOSSWEB_2_0_0_GA_CP14/
Log:
For 4.2.0.GA_CP10, 4.3.0.GA_CP09.
Copied: tags/JBOSSWEB_2_0_0_GA_CP14 (from rev 1506, branches/JBOSSWEB_2_0_0_GA_CP)
13 years, 10 months
JBossWeb SVN: r1506 - tags/JBOSSWEB_2_0_0_GA_CP13.
by jbossweb-commits@lists.jboss.org
Author: jfrederic.clere(a)jboss.com
Date: 2010-07-14 05:01:37 -0400 (Wed, 14 Jul 2010)
New Revision: 1506
Removed:
tags/JBOSSWEB_2_0_0_GA_CP13/JBOSSWEB_2_0_0_GA_CP/
Log:
typo.
13 years, 10 months
JBossWeb SVN: r1505 - tags/JBOSSWEB_2_0_0_GA_CP13.
by jbossweb-commits@lists.jboss.org
Author: jfrederic.clere(a)jboss.com
Date: 2010-07-14 04:32:03 -0400 (Wed, 14 Jul 2010)
New Revision: 1505
Added:
tags/JBOSSWEB_2_0_0_GA_CP13/JBOSSWEB_2_0_0_GA_CP/
Log:
For 4.2.0.GA_CP10, 4.3.0.GA_CP09.
Copied: tags/JBOSSWEB_2_0_0_GA_CP13/JBOSSWEB_2_0_0_GA_CP (from rev 1504, branches/JBOSSWEB_2_0_0_GA_CP)
13 years, 10 months
JBossWeb SVN: r1504 - tags.
by jbossweb-commits@lists.jboss.org
Author: jfrederic.clere(a)jboss.com
Date: 2010-07-14 04:28:15 -0400 (Wed, 14 Jul 2010)
New Revision: 1504
Added:
tags/JBOSSWEB_2_1_9_GA/
Log:
For 5.1.0 CR2 and others.
Copied: tags/JBOSSWEB_2_1_9_GA (from rev 1503, branches/2.1.x)
13 years, 10 months
JBossWeb SVN: r1503 - branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/compiler.
by jbossweb-commits@lists.jboss.org
Author: mmillson
Date: 2010-07-07 17:03:37 -0400 (Wed, 07 Jul 2010)
New Revision: 1503
Modified:
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/compiler/Compiler.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/compiler/JspUtil.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/compiler/Node.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/compiler/TagPluginManager.java
Log:
Fix Jasper duplicate variable names for [JBPAPP-4547].
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/compiler/Compiler.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/compiler/Compiler.java 2010-07-07 20:47:49 UTC (rev 1502)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/compiler/Compiler.java 2010-07-07 21:03:37 UTC (rev 1503)
@@ -144,10 +144,6 @@
ServletWriter writer = null;
try {
-
- // Reset the temporary variable counter for the generator.
- JspUtil.resetTemporaryVariableName();
-
// Parse the file
ParserController parserCtl = new ParserController(ctxt, this);
pageNodes = parserCtl.parse(ctxt.getJspFile());
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/compiler/JspUtil.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/compiler/JspUtil.java 2010-07-07 20:47:49 UTC (rev 1502)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/compiler/JspUtil.java 2010-07-07 21:03:37 UTC (rev 1503)
@@ -610,6 +610,7 @@
/**
* Resets the temporary variable name.
* (not thread-safe)
+ * @deprecated
*/
public static void resetTemporaryVariableName() {
tempSequenceNumber = 0;
@@ -618,6 +619,7 @@
/**
* Generates a new temporary variable name.
* (not thread-safe)
+ * @deprecated
*/
public static String nextTemporaryVariableName() {
return Constants.TEMP_VARIABLE_NAME_PREFIX + (tempSequenceNumber++);
@@ -1141,4 +1143,4 @@
return buf.toString();
}
-}
\ No newline at end of file
+}
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/compiler/Node.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/compiler/Node.java 2010-07-07 20:47:49 UTC (rev 1502)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/compiler/Node.java 2010-07-07 21:03:37 UTC (rev 1503)
@@ -39,6 +39,7 @@
import javax.servlet.jsp.tagext.TryCatchFinally;
import javax.servlet.jsp.tagext.VariableInfo;
+import org.apache.jasper.Constants;
import org.apache.jasper.JasperException;
import org.apache.jasper.compiler.tagplugin.TagPluginContext;
import org.xml.sax.Attributes;
@@ -470,6 +471,11 @@
private boolean isBomPresent;
/*
+ * Sequence number for temporary variables.
+ */
+ private int tempSequenceNumber = 0;
+
+ /*
* Constructor.
*/
Root(Mark start, Node parent, boolean isXmlSyntax) {
@@ -548,6 +554,18 @@
public Root getParentRoot() {
return parentRoot;
}
+
+ /**
+ * Generates a new temporary variable name.
+ */
+ public String nextTemporaryVariableName() {
+ if (parentRoot == null) {
+ return Constants.TEMP_VARIABLE_NAME_PREFIX + (tempSequenceNumber++);
+ } else {
+ return parentRoot.nextTemporaryVariableName();
+ }
+
+ }
}
/**
@@ -1908,6 +1926,9 @@
* (this probably could go elsewhere, but it's convenient here)
*/
public String getTemporaryVariableName() {
+ if (temporaryVariableName == null) {
+ temporaryVariableName = getRoot().nextTemporaryVariableName();
+ }
return temporaryVariableName;
}
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/compiler/TagPluginManager.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/compiler/TagPluginManager.java 2010-07-07 20:47:49 UTC (rev 1502)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/compiler/TagPluginManager.java 2010-07-07 21:03:37 UTC (rev 1503)
@@ -150,91 +150,91 @@
n.setAtSTag(curNodes);
n.setUseTagPlugin(true);
pluginAttributes = new HashMap();
- }
+ }
- public TagPluginContext getParentContext() {
- Node parent = node.getParent();
- if (! (parent instanceof Node.CustomTag)) {
- return null;
- }
- return ((Node.CustomTag) parent).getTagPluginContext();
- }
+ public TagPluginContext getParentContext() {
+ Node parent = node.getParent();
+ if (! (parent instanceof Node.CustomTag)) {
+ return null;
+ }
+ return ((Node.CustomTag) parent).getTagPluginContext();
+ }
- public void setPluginAttribute(String key, Object value) {
- pluginAttributes.put(key, value);
- }
+ public void setPluginAttribute(String key, Object value) {
+ pluginAttributes.put(key, value);
+ }
- public Object getPluginAttribute(String key) {
- return pluginAttributes.get(key);
- }
+ public Object getPluginAttribute(String key) {
+ return pluginAttributes.get(key);
+ }
- public boolean isScriptless() {
- return node.getChildInfo().isScriptless();
- }
+ public boolean isScriptless() {
+ return node.getChildInfo().isScriptless();
+ }
- public boolean isConstantAttribute(String attribute) {
- Node.JspAttribute attr = getNodeAttribute(attribute);
- if (attr == null)
- return false;
- return attr.isLiteral();
- }
+ public boolean isConstantAttribute(String attribute) {
+ Node.JspAttribute attr = getNodeAttribute(attribute);
+ if (attr == null)
+ return false;
+ return attr.isLiteral();
+ }
- public String getConstantAttribute(String attribute) {
- Node.JspAttribute attr = getNodeAttribute(attribute);
+ public String getConstantAttribute(String attribute) {
+ Node.JspAttribute attr = getNodeAttribute(attribute);
if (attr == null)
- return null;
- return attr.getValue();
- }
+ return null;
+ return attr.getValue();
+ }
- public boolean isAttributeSpecified(String attribute) {
- return getNodeAttribute(attribute) != null;
- }
+ public boolean isAttributeSpecified(String attribute) {
+ return getNodeAttribute(attribute) != null;
+ }
- public String getTemporaryVariableName() {
- return JspUtil.nextTemporaryVariableName();
- }
+ public String getTemporaryVariableName() {
+ return node.getRoot().nextTemporaryVariableName();
+ }
- public void generateImport(String imp) {
- pageInfo.addImport(imp);
- }
+ public void generateImport(String imp) {
+ pageInfo.addImport(imp);
+ }
- public void generateDeclaration(String id, String text) {
- if (pageInfo.isPluginDeclared(id)) {
- return;
- }
- curNodes.add(new Node.Declaration(text, node.getStart(), null));
- }
+ public void generateDeclaration(String id, String text) {
+ if (pageInfo.isPluginDeclared(id)) {
+ return;
+ }
+ curNodes.add(new Node.Declaration(text, node.getStart(), null));
+ }
- public void generateJavaSource(String sourceCode) {
- curNodes.add(new Node.Scriptlet(sourceCode, node.getStart(),
- null));
- }
+ public void generateJavaSource(String sourceCode) {
+ curNodes.add(new Node.Scriptlet(sourceCode, node.getStart(),
+ null));
+ }
- public void generateAttribute(String attributeName) {
- curNodes.add(new Node.AttributeGenerator(node.getStart(),
- attributeName,
- node));
- }
+ public void generateAttribute(String attributeName) {
+ curNodes.add(new Node.AttributeGenerator(node.getStart(),
+ attributeName,
+ node));
+ }
- public void dontUseTagPlugin() {
- node.setUseTagPlugin(false);
- }
+ public void dontUseTagPlugin() {
+ node.setUseTagPlugin(false);
+ }
- public void generateBody() {
- // Since we'll generate the body anyway, this is really a nop,
- // except for the fact that it lets us put the Java sources the
- // plugins produce in the correct order (w.r.t the body).
- curNodes = node.getAtETag();
- }
+ public void generateBody() {
+ // Since we'll generate the body anyway, this is really a nop,
+ // except for the fact that it lets us put the Java sources the
+ // plugins produce in the correct order (w.r.t the body).
+ curNodes = node.getAtETag();
+ }
- private Node.JspAttribute getNodeAttribute(String attribute) {
- Node.JspAttribute[] attrs = node.getJspAttributes();
- for (int i=0; attrs != null && i < attrs.length; i++) {
- if (attrs[i].getName().equals(attribute)) {
- return attrs[i];
- }
- }
- return null;
- }
+ private Node.JspAttribute getNodeAttribute(String attribute) {
+ Node.JspAttribute[] attrs = node.getJspAttributes();
+ for (int i=0; attrs != null && i < attrs.length; i++) {
+ if (attrs[i].getName().equals(attribute)) {
+ return attrs[i];
+ }
+ }
+ return null;
+ }
}
}
13 years, 10 months