1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.struts2.dispatcher;
22
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.OutputStream;
26 import java.io.UnsupportedEncodingException;
27 import java.net.URL;
28 import java.net.URLDecoder;
29 import java.util.ArrayList;
30 import java.util.Calendar;
31 import java.util.List;
32 import java.util.StringTokenizer;
33
34 import javax.servlet.http.HttpServletRequest;
35 import javax.servlet.http.HttpServletResponse;
36
37 import org.apache.struts2.StrutsConstants;
38 import org.apache.struts2.dispatcher.ng.HostConfig;
39 import org.apache.struts2.util.ClassLoaderUtils;
40
41 import com.opensymphony.xwork2.inject.Inject;
42 import com.opensymphony.xwork2.util.ClassLoaderUtil;
43 import com.opensymphony.xwork2.util.logging.Logger;
44 import com.opensymphony.xwork2.util.logging.LoggerFactory;
45
46 public class DefaultStaticContentLoader implements StaticContentLoader {
47 /***
48 * Provide a logging instance.
49 */
50 private Logger log;
51
52 /***
53 * Store set of path prefixes to use with static resources.
54 */
55 protected String[] pathPrefixes;
56
57 /***
58 * Store state of StrutsConstants.STRUTS_SERVE_STATIC_CONTENT setting.
59 */
60 protected boolean serveStatic;
61
62 /***
63 * Store state of StrutsConstants.STRUTS_SERVE_STATIC_BROWSER_CACHE setting.
64 */
65 protected boolean serveStaticBrowserCache;
66
67 /***
68 * Provide a formatted date for setting heading information when caching static content.
69 */
70 protected final Calendar lastModifiedCal = Calendar.getInstance();
71
72 /***
73 * Store state of StrutsConstants.STRUTS_I18N_ENCODING setting.
74 */
75 protected String encoding;
76
77
78 /***
79 * Modify state of StrutsConstants.STRUTS_SERVE_STATIC_CONTENT setting.
80 *
81 * @param val
82 * New setting
83 */
84 @Inject(StrutsConstants.STRUTS_SERVE_STATIC_CONTENT)
85 public void setServeStaticContent(String val) {
86 serveStatic = "true".equals(val);
87 }
88
89 /***
90 * Modify state of StrutsConstants.STRUTS_SERVE_STATIC_BROWSER_CACHE
91 * setting.
92 *
93 * @param val
94 * New setting
95 */
96 @Inject(StrutsConstants.STRUTS_SERVE_STATIC_BROWSER_CACHE)
97 public void setServeStaticBrowserCache(String val) {
98 serveStaticBrowserCache = "true".equals(val);
99 }
100
101 /***
102 * Modify state of StrutsConstants.STRUTS_I18N_ENCODING setting.
103 * @param val New setting
104 */
105 @Inject(StrutsConstants.STRUTS_I18N_ENCODING)
106 public void setEncoding(String val) {
107 encoding = val;
108 }
109
110
111
112
113
114
115 public void setHostConfig(HostConfig filterConfig) {
116 String param = filterConfig.getInitParameter("packages");
117 String packages = getAdditionalPackages();
118 if (param != null) {
119 packages = param + " " + packages;
120 }
121 this.pathPrefixes = parse(packages);
122 initLogging(filterConfig);
123 }
124
125 protected String getAdditionalPackages() {
126 return "org.apache.struts2.static template org.apache.struts2.interceptor.debugging static";
127 }
128
129 /***
130 * Create a string array from a comma-delimited list of packages.
131 *
132 * @param packages
133 * A comma-delimited String listing packages
134 * @return A string array of packages
135 */
136 protected String[] parse(String packages) {
137 if (packages == null) {
138 return null;
139 }
140 List<String> pathPrefixes = new ArrayList<String>();
141
142 StringTokenizer st = new StringTokenizer(packages, ", \n\t");
143 while (st.hasMoreTokens()) {
144 String pathPrefix = st.nextToken().replace('.', '/');
145 if (!pathPrefix.endsWith("/")) {
146 pathPrefix += "/";
147 }
148 pathPrefixes.add(pathPrefix);
149 }
150
151 return pathPrefixes.toArray(new String[pathPrefixes.size()]);
152 }
153
154
155
156
157
158
159
160
161 public void findStaticResource(String path, HttpServletRequest request, HttpServletResponse response)
162 throws IOException {
163 String name = cleanupPath(path);
164 for (String pathPrefix : pathPrefixes) {
165 URL resourceUrl = findResource(buildPath(name, pathPrefix));
166 if (resourceUrl != null) {
167 InputStream is = null;
168 try {
169
170 String pathEnding = buildPath(name, pathPrefix);
171 if (resourceUrl.getFile().endsWith(pathEnding))
172 is = resourceUrl.openStream();
173 } catch (Exception ex) {
174
175 continue;
176 }
177
178
179 if (is != null) {
180 process(is, path, request, response);
181 return;
182 }
183 }
184 }
185
186 response.sendError(HttpServletResponse.SC_NOT_FOUND);
187 }
188
189 protected void process(InputStream is, String path, HttpServletRequest request, HttpServletResponse response) throws IOException {
190 if (is != null) {
191 Calendar cal = Calendar.getInstance();
192
193
194 long ifModifiedSince = 0;
195 try {
196 ifModifiedSince = request.getDateHeader("If-Modified-Since");
197 } catch (Exception e) {
198 log.warn("Invalid If-Modified-Since header value: '"
199 + request.getHeader("If-Modified-Since") + "', ignoring");
200 }
201 long lastModifiedMillis = lastModifiedCal.getTimeInMillis();
202 long now = cal.getTimeInMillis();
203 cal.add(Calendar.DAY_OF_MONTH, 1);
204 long expires = cal.getTimeInMillis();
205
206 if (ifModifiedSince > 0 && ifModifiedSince <= lastModifiedMillis) {
207
208
209 response.setDateHeader("Expires", expires);
210 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
211 is.close();
212 return;
213 }
214
215
216 String contentType = getContentType(path);
217 if (contentType != null) {
218 response.setContentType(contentType);
219 }
220
221 if (serveStaticBrowserCache) {
222
223 response.setDateHeader("Date", now);
224 response.setDateHeader("Expires", expires);
225 response.setDateHeader("Retry-After", expires);
226 response.setHeader("Cache-Control", "public");
227 response.setDateHeader("Last-Modified", lastModifiedMillis);
228 } else {
229 response.setHeader("Cache-Control", "no-cache");
230 response.setHeader("Pragma", "no-cache");
231 response.setHeader("Expires", "-1");
232 }
233
234 try {
235 copy(is, response.getOutputStream());
236 } finally {
237 is.close();
238 }
239 return;
240 }
241 }
242
243 private void initLogging(HostConfig filterConfig) {
244 String factoryName = filterConfig.getInitParameter("loggerFactory");
245 if (factoryName != null) {
246 try {
247 Class cls = ClassLoaderUtils.loadClass(factoryName, this.getClass());
248 LoggerFactory fac = (LoggerFactory)cls.newInstance();
249 LoggerFactory.setLoggerFactory(fac);
250 } catch (InstantiationException e) {
251 System.err.println("Unable to instantiate logger factory: "+factoryName+", using default");
252 e.printStackTrace();
253 } catch (IllegalAccessException e) {
254 System.err.println("Unable to access logger factory: "+factoryName+", using default");
255 e.printStackTrace();
256 } catch (ClassNotFoundException e) {
257 System.err.println("Unable to locate logger factory class: "+factoryName+", using default");
258 e.printStackTrace();
259 }
260 }
261
262 log = LoggerFactory.getLogger(FilterDispatcher.class);
263
264 }
265
266 /***
267 * Look for a static resource in the classpath.
268 *
269 * @param path The resource path
270 * @return The inputstream of the resource
271 * @throws IOException If there is a problem locating the resource
272 */
273 protected URL findResource(String path) throws IOException {
274 return ClassLoaderUtil.getResource(path, getClass());
275 }
276
277 /***
278 * @param name resource name
279 * @param packagePrefix The package prefix to use to locate the resource
280 * @return full path
281 * @throws UnsupportedEncodingException
282 * @throws IOException
283 */
284 protected String buildPath(String name, String packagePrefix) throws UnsupportedEncodingException {
285 String resourcePath;
286 if (packagePrefix.endsWith("/") && name.startsWith("/")) {
287 resourcePath = packagePrefix + name.substring(1);
288 } else {
289 resourcePath = packagePrefix + name;
290 }
291
292 return URLDecoder.decode(resourcePath, encoding);
293 }
294
295
296
297 /***
298 * Determine the content type for the resource name.
299 *
300 * @param name The resource name
301 * @return The mime type
302 */
303 protected String getContentType(String name) {
304
305
306 if (name.endsWith(".js")) {
307 return "text/javascript";
308 } else if (name.endsWith(".css")) {
309 return "text/css";
310 } else if (name.endsWith(".html")) {
311 return "text/html";
312 } else if (name.endsWith(".txt")) {
313 return "text/plain";
314 } else if (name.endsWith(".gif")) {
315 return "image/gif";
316 } else if (name.endsWith(".jpg") || name.endsWith(".jpeg")) {
317 return "image/jpeg";
318 } else if (name.endsWith(".png")) {
319 return "image/png";
320 } else {
321 return null;
322 }
323 }
324
325 /***
326 * Copy bytes from the input stream to the output stream.
327 *
328 * @param input
329 * The input stream
330 * @param output
331 * The output stream
332 * @throws IOException
333 * If anything goes wrong
334 */
335 protected void copy(InputStream input, OutputStream output) throws IOException {
336 final byte[] buffer = new byte[4096];
337 int n;
338 while (-1 != (n = input.read(buffer))) {
339 output.write(buffer, 0, n);
340 }
341 output.flush();
342 }
343
344 public boolean canHandle(String resourcePath) {
345 return serveStatic && (resourcePath.startsWith("/struts") || resourcePath.startsWith("/static"));
346 }
347
348 /***
349 * @param path requested path
350 * @return path without leading "/struts" or "/static"
351 */
352 protected String cleanupPath(String path) {
353
354 return path.substring(7);
355 }
356 }