KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > web > servlet > ResourceServlet


1 /*
2  * Copyright 2002-2005 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.web.servlet;
18
19 import java.io.File JavaDoc;
20 import java.io.IOException JavaDoc;
21
22 import javax.servlet.RequestDispatcher JavaDoc;
23 import javax.servlet.ServletException JavaDoc;
24 import javax.servlet.http.HttpServletRequest JavaDoc;
25 import javax.servlet.http.HttpServletResponse JavaDoc;
26
27 import org.springframework.util.AntPathMatcher;
28 import org.springframework.util.PathMatcher;
29 import org.springframework.util.StringUtils;
30 import org.springframework.web.context.support.ServletContextResource;
31
32 /**
33  * Simple servlet that can expose an internal resource, including a
34  * default URL if the specified resource is not found. An alternative,
35  * for example, to trying and catching exceptions when using JSP include.
36  *
37  * <p>A further usage of this servlet is the ability to apply last-modified
38  * timestamps to quasi-static resources (typically JSPs). This can happen
39  * as bridge to parameter-specified resources, or as proxy for a specific
40  * target resource (or a list of specific target resources to combine).
41  *
42  * <p>A typical usage would map a URL like "/ResourceServlet" onto an instance
43  * of this servlet, and use the "JSP include" action to include this URL,
44  * with the "resource" parameter indicating the actual target path in the WAR.
45  *
46  * <p>The <code>defaultUrl</code> property can be set to the internal
47  * resource path of a default URL, to be rendered when the target resource
48  * is not found or not specified in the first place.
49  *
50  * <p>The "resource" parameter and the <code>defaultUrl</code> property can
51  * also specify a list of target resources to combine. Those resources will be
52  * included one by one to build the response. If last-modified determination
53  * is active, the newest timestamp among those files will be used.
54  *
55  * <p>The <code>allowedResources</code> property can be set to a URL
56  * pattern of resources that should be available via this servlet.
57  * If not set, any target resource can be requested, including resources
58  * in the WEB-INF directory!
59  *
60  * <p>If using this servlet for direct access rather than via includes,
61  * the <code>contentType</code> property should be specified to apply a
62  * proper content type. Note that a content type header in the target JSP will
63  * be ignored when including the resource via a RequestDispatcher include.
64  *
65  * <p>To apply last-modified timestamps for the target resource, set the
66  * <code>applyLastModified</code> property to true. This servlet will then
67  * return the file timestamp of the target resource as last-modified value,
68  * falling back to the startup time of this servlet if not retrievable.
69  *
70  * <p>Note that applying the last-modified timestamp in the above fashion
71  * just makes sense if the target resource does not generate content that
72  * depends on the HttpSession or cookies; it is just allowed to evaluate
73  * request parameters.
74  *
75  * <p>A typical case for such last-modified usage is a JSP that just makes
76  * minimal usage of basic means like includes or message resolution to
77  * build quasi-static content. Regenerating such content on every request
78  * is unnecessary; it can be cached as long as the file hasn't changed.
79  *
80  * <p>Note that this servlet will apply the last-modified timestamp if you
81  * tell it to do so: It's your decision whether the content of the target
82  * resource can be cached in such a fashion. Typical use cases are helper
83  * resources that are not fronted by a controller, like JavaScript files
84  * that are generated by a JSP (without depending on the HttpSession).
85  *
86  * @author Rod Johnson
87  * @author Juergen Hoeller
88  * @see #setDefaultUrl
89  * @see #setAllowedResources
90  * @see #setApplyLastModified
91  */

92 public class ResourceServlet extends HttpServletBean {
93
94     /**
95      * Any number of these characters are considered delimiters
96      * between multiple resource paths in a single String value.
97      */

98     public static final String JavaDoc RESOURCE_URL_DELIMITERS = ",; \t\n";
99
100     /**
101      * Name of the parameter that must contain the actual resource path.
102      */

103     public static final String JavaDoc RESOURCE_PARAM_NAME = "resource";
104
105
106     private String JavaDoc defaultUrl;
107
108     private String JavaDoc allowedResources;
109
110     private String JavaDoc contentType;
111
112     private boolean applyLastModified = false;
113
114     private PathMatcher pathMatcher;
115
116     private long startupTime;
117
118
119     /**
120      * Set the URL within the current web application from which to
121      * include content if the requested path isn't found, or if none
122      * is specified in the first place.
123      * <p>If specifying multiple URLs, they will be included one by one
124      * to build the response. If last-modified determination is active,
125      * the newest timestamp among those files will be used.
126      * @see #setApplyLastModified
127      */

128     public void setDefaultUrl(String JavaDoc defaultUrl) {
129         this.defaultUrl = defaultUrl;
130     }
131
132     /**
133      * Set allowed resources as URL pattern, e.g. "/WEB-INF/res/*.jsp",
134      * The parameter can be any Ant-style pattern parsable by AntPathMatcher.
135      * @see org.springframework.util.AntPathMatcher
136      */

137     public void setAllowedResources(String JavaDoc allowedResources) {
138         this.allowedResources = allowedResources;
139     }
140
141     /**
142      * Set the content type of the target resource (typically a JSP).
143      * Default is none, which is appropriate when including resources.
144      * <p>For directly accessing resources, for example to leverage this
145      * servlet's last-modified support, specify a content type here.
146      * Note that a content type header in the target JSP will be ignored
147      * when including the resource via a RequestDispatcher include.
148      */

149     public void setContentType(String JavaDoc contentType) {
150         this.contentType = contentType;
151     }
152
153     /**
154      * Set whether to apply the file timestamp of the target resource
155      * as last-modified value. Default is "false".
156      * <p>This is mainly intended for JSP targets that don't generate
157      * session-specific or database-driven content: Such files can be
158      * cached by the browser as long as the last-modified timestamp
159      * of the JSP file doesn't change.
160      * <p>This will only work correctly with expanded WAR files that
161      * allow access to the file timestamps. Else, the startup time
162      * of this servlet is returned.
163      */

164     public void setApplyLastModified(boolean applyLastModified) {
165         this.applyLastModified = applyLastModified;
166     }
167
168
169     /**
170      * Remember the startup time, using no last-modified time before it.
171      */

172     protected void initServletBean() {
173         this.pathMatcher = getPathMatcher();
174         this.startupTime = System.currentTimeMillis();
175     }
176
177     /**
178      * Return a PathMatcher to use for matching the "allowedResources" URL pattern.
179      * Default is AntPathMatcher.
180      * @see #setAllowedResources
181      * @see org.springframework.util.AntPathMatcher
182      */

183     protected PathMatcher getPathMatcher() {
184         return new AntPathMatcher();
185     }
186
187
188     /**
189      * Determine the URL of the target resource and include it.
190      * @see #determineResourceUrl
191      */

192     protected final void doGet(HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response)
193         throws ServletException JavaDoc, IOException JavaDoc {
194
195         // determine URL of resource to include
196
String JavaDoc resourceUrl = determineResourceUrl(request);
197
198         if (resourceUrl != null) {
199             try {
200                 doInclude(request, response, resourceUrl);
201             }
202             catch (ServletException JavaDoc ex) {
203                 if (logger.isWarnEnabled()) {
204                     logger.warn("Failed to include content of resource [" + resourceUrl + "]", ex);
205                 }
206                 // Try including default URL if appropriate.
207
if (!includeDefaultUrl(request, response)) {
208                     throw ex;
209                 }
210             }
211             catch (IOException JavaDoc ex) {
212                 if (logger.isWarnEnabled()) {
213                     logger.warn("Failed to include content of resource [" + resourceUrl + "]", ex);
214                 }
215                 // Try including default URL if appropriate.
216
if (!includeDefaultUrl(request, response)) {
217                     throw ex;
218                 }
219             }
220         }
221
222         // no resource URL specified -> try to include default URL.
223
else if (!includeDefaultUrl(request, response)) {
224             throw new ServletException JavaDoc("No target resource URL found for request");
225         }
226     }
227
228     /**
229      * Determine the URL of the target resource of this request.
230      * <p>Default implementation returns the value of the "resource" parameter.
231      * Can be overridden in subclasses.
232      * @param request current HTTP request
233      * @return the URL of the target resource, or <code>null</code> if none found
234      * @see #RESOURCE_PARAM_NAME
235      */

236     protected String JavaDoc determineResourceUrl(HttpServletRequest JavaDoc request) {
237         return request.getParameter(RESOURCE_PARAM_NAME);
238     }
239
240     /**
241      * Include the specified default URL, if appropriate.
242      * @param request current HTTP request
243      * @param response current HTTP response
244      * @return whether a default URL was included
245      * @throws ServletException if thrown by the RequestDispatcher
246      * @throws IOException if thrown by the RequestDispatcher
247      */

248     private boolean includeDefaultUrl(HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response)
249         throws ServletException JavaDoc, IOException JavaDoc {
250         if (this.defaultUrl == null) {
251             return false;
252         }
253         doInclude(request, response, this.defaultUrl);
254         return true;
255     }
256
257     /**
258      * Include the specified resource via the RequestDispatcher.
259      * @param request current HTTP request
260      * @param response current HTTP response
261      * @param resourceUrl the URL of the target resource
262      * @throws ServletException if thrown by the RequestDispatcher
263      * @throws IOException if thrown by the RequestDispatcher
264      */

265     private void doInclude(HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response, String JavaDoc resourceUrl)
266         throws ServletException JavaDoc, IOException JavaDoc {
267
268         if (this.contentType != null) {
269             response.setContentType(this.contentType);
270         }
271         String JavaDoc[] resourceUrls =
272             StringUtils.tokenizeToStringArray(resourceUrl, RESOURCE_URL_DELIMITERS);
273         for (int i = 0; i < resourceUrls.length; i++) {
274             // check whether URL matches allowed resources
275
if (this.allowedResources != null && !this.pathMatcher.match(this.allowedResources, resourceUrls[i])) {
276                 throw new ServletException JavaDoc("Resource [" + resourceUrls[i] +
277                         "] does not match allowed pattern [" + this.allowedResources + "]");
278             }
279             if (logger.isDebugEnabled()) {
280                 logger.debug("Including resource [" + resourceUrls[i] + "]");
281             }
282             RequestDispatcher JavaDoc rd = request.getRequestDispatcher(resourceUrls[i]);
283             rd.include(request, response);
284         }
285     }
286
287     /**
288      * Return the last-modified timestamp of the file that corresponds
289      * to the target resource URL (i.e. typically the request ".jsp" file).
290      * Will simply return -1 if "applyLastModified" is false (the default).
291      * <p>Returns no last-modified date before the startup time of this servlet,
292      * to allow for message resolution etc that influences JSP contents,
293      * assuming that those background resources might have changed on restart.
294      * <p>Returns the startup time of this servlet if the file that corresponds
295      * to the target resource URL coudln't be resolved (for example, because
296      * the WAR is not expanded).
297      * @see #determineResourceUrl
298      * @see #getFileTimestamp
299      */

300     protected final long getLastModified(HttpServletRequest JavaDoc request) {
301         if (this.applyLastModified) {
302             String JavaDoc resourceUrl = determineResourceUrl(request);
303             if (resourceUrl == null) {
304                 resourceUrl = this.defaultUrl;
305             }
306             if (resourceUrl != null) {
307                 String JavaDoc[] resourceUrls = StringUtils.tokenizeToStringArray(resourceUrl, RESOURCE_URL_DELIMITERS);
308                 long latestTimestamp = -1;
309                 for (int i = 0; i < resourceUrls.length; i++) {
310                     long timestamp = getFileTimestamp(resourceUrls[i]);
311                     if (timestamp > latestTimestamp) {
312                         latestTimestamp = timestamp;
313                     }
314                 }
315                 return (latestTimestamp > this.startupTime ? latestTimestamp : this.startupTime);
316             }
317         }
318         return -1;
319     }
320
321     /**
322      * Return the file timestamp for the given resource.
323      * @param resourceUrl the URL of the resource
324      * @return the file timestamp in milliseconds,
325      * or -1 if not determinable
326      */

327     protected long getFileTimestamp(String JavaDoc resourceUrl) {
328         try {
329             File JavaDoc resource = new ServletContextResource(getServletContext(), resourceUrl).getFile();
330             long lastModifiedTime = resource.lastModified();
331             if (logger.isDebugEnabled()) {
332                 logger.debug("Last-modified timestamp of resource file [" + resource.getAbsolutePath() +
333                         "] is [" + lastModifiedTime + "]");
334             }
335             return lastModifiedTime;
336         }
337         catch (IOException JavaDoc ex) {
338             logger.warn("Couldn't retrieve lastModified timestamp of resource [" + resourceUrl +
339                     "] - returning ResourceServlet startup time");
340             return -1;
341         }
342     }
343
344 }
345
Popular Tags