| 1 |
/* |
| 2 |
* $Id$ |
| 3 |
* |
| 4 |
* Licensed to the Apache Software Foundation (ASF) under one |
| 5 |
* or more contributor license agreements. See the NOTICE file |
| 6 |
* distributed with this work for additional information |
| 7 |
* regarding copyright ownership. The ASF licenses this file |
| 8 |
* to you under the Apache License, Version 2.0 (the |
| 9 |
* "License"); you may not use this file except in compliance |
| 10 |
* with the License. You may obtain a copy of the License at |
| 11 |
* |
| 12 |
* http://www.apache.org/licenses/LICENSE-2.0 |
| 13 |
* |
| 14 |
* Unless required by applicable law or agreed to in writing, |
| 15 |
* software distributed under the License is distributed on an |
| 16 |
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 17 |
* KIND, either express or implied. See the License for the |
| 18 |
* specific language governing permissions and limitations |
| 19 |
* under the License. |
| 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.net.URLDecoder; |
| 27 |
import java.util.ArrayList; |
| 28 |
import java.util.Calendar; |
| 29 |
import java.util.Enumeration; |
| 30 |
import java.util.HashMap; |
| 31 |
import java.util.List; |
| 32 |
import java.util.Map; |
| 33 |
import java.util.StringTokenizer; |
| 34 |
|
| 35 |
import javax.servlet.Filter; |
| 36 |
import javax.servlet.FilterChain; |
| 37 |
import javax.servlet.FilterConfig; |
| 38 |
import javax.servlet.ServletContext; |
| 39 |
import javax.servlet.ServletException; |
| 40 |
import javax.servlet.ServletRequest; |
| 41 |
import javax.servlet.ServletResponse; |
| 42 |
import javax.servlet.http.HttpServletRequest; |
| 43 |
import javax.servlet.http.HttpServletResponse; |
| 44 |
|
| 45 |
import org.apache.commons.logging.Log; |
| 46 |
import org.apache.commons.logging.LogFactory; |
| 47 |
import org.apache.struts2.RequestUtils; |
| 48 |
import org.apache.struts2.StrutsConstants; |
| 49 |
import org.apache.struts2.StrutsStatics; |
| 50 |
import org.apache.struts2.dispatcher.mapper.ActionMapper; |
| 51 |
import org.apache.struts2.dispatcher.mapper.ActionMapping; |
| 52 |
|
| 53 |
import com.opensymphony.xwork2.inject.Inject; |
| 54 |
import com.opensymphony.xwork2.util.ClassLoaderUtil; |
| 55 |
import com.opensymphony.xwork2.util.profiling.UtilTimerStack; |
| 56 |
import com.opensymphony.xwork2.ActionContext; |
| 57 |
|
| 58 |
/** |
| 59 |
* Master filter for Struts that handles four distinct |
| 60 |
* responsibilities: |
| 61 |
* |
| 62 |
* <ul> |
| 63 |
* |
| 64 |
* <li>Executing actions</li> |
| 65 |
* |
| 66 |
* <li>Cleaning up the {@link ActionContext} (see note)</li> |
| 67 |
* |
| 68 |
* <li>Serving static content</li> |
| 69 |
* |
| 70 |
* <li>Kicking off XWork's interceptor chain for the request lifecycle</li> |
| 71 |
* |
| 72 |
* </ul> |
| 73 |
* |
| 74 |
* <p/> <b>IMPORTANT</b>: this filter must be mapped to all requests. Unless you know exactly what you are doing, always |
| 75 |
* map to this URL pattern: /* |
| 76 |
* |
| 77 |
* <p/> <b>Executing actions</b> |
| 78 |
* |
| 79 |
* <p/> This filter executes actions by consulting the {@link ActionMapper} and determining if the requested URL should |
| 80 |
* invoke an action. If the mapper indicates it should, <b>the rest of the filter chain is stopped</b> and the action is |
| 81 |
* invoked. This is important, as it means that filters like the SiteMesh filter must be placed <b>before</b> this |
| 82 |
* filter or they will not be able to decorate the output of actions. |
| 83 |
* |
| 84 |
* <p/> <b>Cleaning up the {@link ActionContext}</b> |
| 85 |
* |
| 86 |
* <p/> This filter will also automatically clean up the {@link ActionContext} for you, ensuring that no memory leaks |
| 87 |
* take place. However, this can sometimes cause problems integrating with other products like SiteMesh. See {@link |
| 88 |
* ActionContextCleanUp} for more information on how to deal with this. |
| 89 |
* |
| 90 |
* <p/> <b>Serving static content</b> |
| 91 |
* |
| 92 |
* <p/> This filter also serves common static content needed when using various parts of Struts, such as JavaScript |
| 93 |
* files, CSS files, etc. It works by looking for requests to /struts/*, and then mapping the value after "/struts/" |
| 94 |
* to common packages in Struts and, optionally, in your class path. By default, the following packages are |
| 95 |
* automatically searched: |
| 96 |
* |
| 97 |
* <ul> |
| 98 |
* |
| 99 |
* <li>org.apache.struts2.static</li> |
| 100 |
* |
| 101 |
* <li>template</li> |
| 102 |
* |
| 103 |
* </ul> |
| 104 |
* |
| 105 |
* <p/> This means that you can simply request /struts/xhtml/styles.css and the XHTML UI theme's default stylesheet |
| 106 |
* will be returned. Likewise, many of the AJAX UI components require various JavaScript files, which are found in the |
| 107 |
* org.apache.struts2.static package. If you wish to add additional packages to be searched, you can add a comma |
| 108 |
* separated (space, tab and new line will do as well) list in the filter init parameter named "packages". <b>Be |
| 109 |
* careful</b>, however, to expose any packages that may have sensitive information, such as properties file with |
| 110 |
* database access credentials. |
| 111 |
* |
| 112 |
* <p/> |
| 113 |
* |
| 114 |
* <p> |
| 115 |
* |
| 116 |
* This filter supports the following init-params: |
| 117 |
* <!-- START SNIPPET: params --> |
| 118 |
* |
| 119 |
* <ul> |
| 120 |
* |
| 121 |
* <li><b>config</b> - a comma-delimited list of XML configuration files to load.</li> |
| 122 |
* |
| 123 |
* <li><b>actionPackages</b> - a comma-delimited list of Java packages to scan for Actions.</li> |
| 124 |
* |
| 125 |
* <li><b>configProviders</b> - a comma-delimited list of Java classes that implement the |
| 126 |
* {@link ConfigurationProvider} interface that should be used for building the {@link Configuration}.</li> |
| 127 |
* |
| 128 |
* <li><b>*</b> - any other parameters are treated as framework constants.</li> |
| 129 |
* |
| 130 |
* </ul> |
| 131 |
* |
| 132 |
* <!-- END SNIPPET: params --> |
| 133 |
* |
| 134 |
* </p> |
| 135 |
* |
| 136 |
* To use a custom {@link Dispatcher}, the <code>createDispatcher()</code> method could be overriden by |
| 137 |
* the subclass. |
| 138 |
* |
| 139 |
* @see ActionMapper |
| 140 |
* @see ActionContextCleanUp |
| 141 |
* |
| 142 |
* @version $Date$ $Id$ |
| 143 |
*/ |
| 144 |
public class FilterDispatcher implements StrutsStatics, Filter { |
| 145 |
|
| 146 |
/** |
| 147 |
* Provide a logging instance. |
| 148 |
*/ |
| 149 |
private static final Log LOG = LogFactory.getLog(FilterDispatcher.class); |
| 150 |
|
| 151 |
/** |
| 152 |
* Store set of path prefixes to use with static resources. |
| 153 |
*/ |
| 154 |
private String[] pathPrefixes; |
| 155 |
|
| 156 |
/** |
| 157 |
* Provide a formatted date for setting heading information when caching static content. |
| 158 |
*/ |
| 159 |
private final Calendar lastModifiedCal = Calendar.getInstance(); |
| 160 |
|
| 161 |
/** |
| 162 |
* Store state of StrutsConstants.STRUTS_SERVE_STATIC_CONTENT setting. |
| 163 |
*/ |
| 164 |
private static boolean serveStatic; |
| 165 |
|
| 166 |
/** |
| 167 |
* Store state of StrutsConstants.STRUTS_SERVE_STATIC_BROWSER_CACHE setting. |
| 168 |
*/ |
| 169 |
private static boolean serveStaticBrowserCache; |
| 170 |
|
| 171 |
/** |
| 172 |
* Store state of StrutsConstants.STRUTS_I18N_ENCODING setting. |
| 173 |
*/ |
| 174 |
private static String encoding; |
| 175 |
|
| 176 |
/** |
| 177 |
* Provide ActionMapper instance, set by injection. |
| 178 |
*/ |
| 179 |
private static ActionMapper actionMapper; |
| 180 |
|
| 181 |
/** |
| 182 |
* Provide FilterConfig instance, set on init. |
| 183 |
*/ |
| 184 |
private FilterConfig filterConfig; |
| 185 |
|
| 186 |
/** |
| 187 |
* Expose Dispatcher instance to subclass. |
| 188 |
*/ |
| 189 |
protected Dispatcher dispatcher; |
| 190 |
|
| 191 |
/** |
| 192 |
* Initializes the filter by creating a default dispatcher |
| 193 |
* and setting the default packages for static resources. |
| 194 |
* |
| 195 |
* @param filterConfig The filter configuration |
| 196 |
*/ |
| 197 |
public void init(FilterConfig filterConfig) throws ServletException { |
| 198 |
this.filterConfig = filterConfig; |
| 199 |
|
| 200 |
dispatcher = createDispatcher(filterConfig); |
| 201 |
dispatcher.init(); |
| 202 |
|
| 203 |
String param = filterConfig.getInitParameter("packages"); |
| 204 |
String packages = "org.apache.struts2.static template org.apache.struts2.interceptor.debugging"; |
| 205 |
if (param != null) { |
| 206 |
packages = param + " " + packages; |
| 207 |
} |
| 208 |
this.pathPrefixes = parse(packages); |
| 209 |
} |
| 210 |
|
| 211 |
/** |
| 212 |
* Calls dispatcher.cleanup, |
| 213 |
* which in turn releases local threads and destroys any DispatchListeners. |
| 214 |
* |
| 215 |
* @see javax.servlet.Filter#destroy() |
| 216 |
*/ |
| 217 |
public void destroy() { |
| 218 |
if (dispatcher == null) { |
| 219 |
LOG.warn("something is seriously wrong, Dispatcher is not initialized (null) "); |
| 220 |
} else { |
| 221 |
dispatcher.cleanup(); |
| 222 |
} |
| 223 |
} |
| 224 |
|
| 225 |
/** |
| 226 |
* Create a default {@link Dispatcher} that subclasses can override |
| 227 |
* with a custom Dispatcher, if needed. |
| 228 |
* |
| 229 |
* @param filterConfig Our FilterConfig |
| 230 |
* @return Initialized Dispatcher |
| 231 |
*/ |
| 232 |
protected Dispatcher createDispatcher(FilterConfig filterConfig) { |
| 233 |
Map<String,String> params = new HashMap<String,String>(); |
| 234 |
for (Enumeration e = filterConfig.getInitParameterNames(); e.hasMoreElements(); ) { |
| 235 |
String name = (String) e.nextElement(); |
| 236 |
String value = filterConfig.getInitParameter(name); |
| 237 |
params.put(name, value); |
| 238 |
} |
| 239 |
return new Dispatcher(filterConfig.getServletContext(), params); |
| 240 |
} |
| 241 |
|
| 242 |
/** |
| 243 |
* Modify state of StrutsConstants.STRUTS_SERVE_STATIC_CONTENT setting. |
| 244 |
* @param val New setting |
| 245 |
*/ |
| 246 |
@Inject(StrutsConstants.STRUTS_SERVE_STATIC_CONTENT) |
| 247 |
public static void setServeStaticContent(String val) { |
| 248 |
serveStatic = "true".equals(val); |
| 249 |
} |
| 250 |
|
| 251 |
/** |
| 252 |
* Modify state of StrutsConstants.STRUTS_SERVE_STATIC_BROWSER_CACHE setting. |
| 253 |
* @param val New setting |
| 254 |
*/ |
| 255 |
@Inject(StrutsConstants.STRUTS_SERVE_STATIC_BROWSER_CACHE) |
| 256 |
public static void setServeStaticBrowserCache(String val) { |
| 257 |
serveStaticBrowserCache = "true".equals(val); |
| 258 |
} |
| 259 |
|
| 260 |
/** |
| 261 |
* Modify state of StrutsConstants.STRUTS_I18N_ENCODING setting. |
| 262 |
* @param val New setting |
| 263 |
*/ |
| 264 |
@Inject(StrutsConstants.STRUTS_I18N_ENCODING) |
| 265 |
public static void setEncoding(String val) { |
| 266 |
encoding = val; |
| 267 |
} |
| 268 |
|
| 269 |
/** |
| 270 |
* Modify ActionMapper instance. |
| 271 |
* @param mapper New instance |
| 272 |
*/ |
| 273 |
@Inject |
| 274 |
public static void setActionMapper(ActionMapper mapper) { |
| 275 |
actionMapper = mapper; |
| 276 |
} |
| 277 |
|
| 278 |
/** |
| 279 |
* Provide a workaround for some versions of WebLogic. |
| 280 |
* <p/> |
| 281 |
* Servlet 2.3 specifies that the servlet context can be retrieved from the session. Unfortunately, some versions of |
| 282 |
* WebLogic can only retrieve the servlet context from the filter config. Hence, this method enables subclasses to |
| 283 |
* retrieve the servlet context from other sources. |
| 284 |
* |
| 285 |
* @return the servlet context. |
| 286 |
*/ |
| 287 |
protected ServletContext getServletContext() { |
| 288 |
return filterConfig.getServletContext(); |
| 289 |
} |
| 290 |
|
| 291 |
/** |
| 292 |
* Expose the FilterConfig instance. |
| 293 |
* |
| 294 |
* @return Our FilterConfit instance |
| 295 |
*/ |
| 296 |
protected FilterConfig getFilterConfig() { |
| 297 |
return filterConfig; |
| 298 |
} |
| 299 |
|
| 300 |
/** |
| 301 |
* Wrap and return the given request, if needed, so as to to transparently |
| 302 |
* handle multipart data as a wrapped class around the given request. |
| 303 |
* |
| 304 |
* @param request Our ServletRequest object |
| 305 |
* @param response Our ServerResponse object |
| 306 |
* @return Wrapped HttpServletRequest object |
| 307 |
* @throws ServletException on any error |
| 308 |
*/ |
| 309 |
protected HttpServletRequest prepareDispatcherAndWrapRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException { |
| 310 |
|
| 311 |
Dispatcher du = Dispatcher.getInstance(); |
| 312 |
|
| 313 |
// Prepare and wrap the request if the cleanup filter hasn't already, cleanup filter should be |
| 314 |
// configured first before struts2 dispatcher filter, hence when its cleanup filter's turn, |
| 315 |
// static instance of Dispatcher should be null. |
| 316 |
if (du == null) { |
| 317 |
|
| 318 |
Dispatcher.setInstance(dispatcher); |
| 319 |
|
| 320 |
// prepare the request no matter what - this ensures that the proper character encoding |
| 321 |
// is used before invoking the mapper (see WW-9127) |
| 322 |
dispatcher.prepare(request, response); |
| 323 |
} else { |
| 324 |
dispatcher = du; |
| 325 |
} |
| 326 |
|
| 327 |
try { |
| 328 |
// Wrap request first, just in case it is multipart/form-data |
| 329 |
// parameters might not be accessible through before encoding (ww-1278) |
| 330 |
request = dispatcher.wrapRequest(request, getServletContext()); |
| 331 |
} catch (IOException e) { |
| 332 |
String message = "Could not wrap servlet request with MultipartRequestWrapper!"; |
| 333 |
LOG.error(message, e); |
| 334 |
throw new ServletException(message, e); |
| 335 |
} |
| 336 |
|
| 337 |
return request; |
| 338 |
} |
| 339 |
|
| 340 |
/** |
| 341 |
* Create a string array from a comma-delimited list of packages. |
| 342 |
* |
| 343 |
* @param packages A comma-delimited String listing packages |
| 344 |
* @return A string array of packages |
| 345 |
*/ |
| 346 |
protected String[] parse(String packages) { |
| 347 |
if (packages == null) { |
| 348 |
return null; |
| 349 |
} |
| 350 |
List<String> pathPrefixes = new ArrayList<String>(); |
| 351 |
|
| 352 |
StringTokenizer st = new StringTokenizer(packages, ", \n\t"); |
| 353 |
while (st.hasMoreTokens()) { |
| 354 |
String pathPrefix = st.nextToken().replace('.', '/'); |
| 355 |
if (!pathPrefix.endsWith("/")) { |
| 356 |
pathPrefix += "/"; |
| 357 |
} |
| 358 |
pathPrefixes.add(pathPrefix); |
| 359 |
} |
| 360 |
|
| 361 |
return pathPrefixes.toArray(new String[pathPrefixes.size()]); |
| 362 |
} |
| 363 |
|
| 364 |
|
| 365 |
/** |
| 366 |
* Process an action or handle a request a static resource. |
| 367 |
* <p/> |
| 368 |
* The filter tries to match the request to an action mapping. |
| 369 |
* If mapping is found, the action processes is delegated to the dispatcher's serviceAction method. |
| 370 |
* If action processing fails, doFilter will try to create an error page via the dispatcher. |
| 371 |
* <p/> |
| 372 |
* Otherwise, if the request is for a static resource, |
| 373 |
* the resource is copied directly to the response, with the appropriate caching headers set. |
| 374 |
* <p/> |
| 375 |
* If the request does not match an action mapping, or a static resource page, |
| 376 |
* then it passes through. |
| 377 |
* |
| 378 |
* @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) |
| 379 |
*/ |
| 380 |
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { |
| 381 |
|
| 382 |
|
| 383 |
HttpServletRequest request = (HttpServletRequest) req; |
| 384 |
HttpServletResponse response = (HttpServletResponse) res; |
| 385 |
ServletContext servletContext = getServletContext(); |
| 386 |
|
| 387 |
String timerKey = "FilterDispatcher_doFilter: "; |
| 388 |
try { |
| 389 |
UtilTimerStack.push(timerKey); |
| 390 |
request = prepareDispatcherAndWrapRequest(request, response); |
| 391 |
ActionMapping mapping; |
| 392 |
try { |
| 393 |
mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager()); |
| 394 |
} catch (Exception ex) { |
| 395 |
LOG.error("error getting ActionMapping", ex); |
| 396 |
dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex); |
| 397 |
ActionContextCleanUp.cleanUp(req); |
| 398 |
return; |
| 399 |
} |
| 400 |
|
| 401 |
if (mapping == null) { |
| 402 |
// there is no action in this request, should we look for a static resource? |
| 403 |
String resourcePath = RequestUtils.getServletPath(request); |
| 404 |
|
| 405 |
if ("".equals(resourcePath) && null != request.getPathInfo()) { |
| 406 |
resourcePath = request.getPathInfo(); |
| 407 |
} |
| 408 |
|
| 409 |
if (serveStatic && resourcePath.startsWith("/struts")) { |
| 410 |
String name = resourcePath.substring("/struts".length()); |
| 411 |
findStaticResource(name, request, response); |
| 412 |
} else { |
| 413 |
// this is a normal request, let it pass through |
| 414 |
chain.doFilter(request, response); |
| 415 |
} |
| 416 |
// The framework did its job here |
| 417 |
return; |
| 418 |
} |
| 419 |
|
| 420 |
try { |
| 421 |
dispatcher.serviceAction(request, response, servletContext, mapping); |
| 422 |
} finally { |
| 423 |
ActionContextCleanUp.cleanUp(req); |
| 424 |
} |
| 425 |
} |
| 426 |
finally { |
| 427 |
UtilTimerStack.pop(timerKey); |
| 428 |
} |
| 429 |
} |
| 430 |
|
| 431 |
/** |
| 432 |
* Locate a static resource and copy directly to the response, |
| 433 |
* setting the appropriate caching headers. |
| 434 |
* |
| 435 |
* @param name The resource name |
| 436 |
* @param request The request |
| 437 |
* @param response The response |
| 438 |
* @throws IOException If anything goes wrong |
| 439 |
*/ |
| 440 |
protected void findStaticResource(String name, HttpServletRequest request, HttpServletResponse response) throws IOException { |
| 441 |
if (!name.endsWith(".class")) { |
| 442 |
for (String pathPrefix : pathPrefixes) { |
| 443 |
InputStream is = findInputStream(name, pathPrefix); |
| 444 |
if (is != null) { |
| 445 |
Calendar cal = Calendar.getInstance(); |
| 446 |
|
| 447 |
// check for if-modified-since, prior to any other headers |
| 448 |
long ifModifiedSince = 0; |
| 449 |
try { |
| 450 |
ifModifiedSince = request.getDateHeader("If-Modified-Since"); |
| 451 |
} catch (Exception e) { |
| 452 |
LOG.warn("Invalid If-Modified-Since header value: '" + request.getHeader("If-Modified-Since") + "', ignoring"); |
| 453 |
} |
| 454 |
long lastModifiedMillis = lastModifiedCal.getTimeInMillis(); |
| 455 |
long now = cal.getTimeInMillis(); |
| 456 |
cal.add(Calendar.DAY_OF_MONTH, 1); |
| 457 |
long expires = cal.getTimeInMillis(); |
| 458 |
|
| 459 |
if (ifModifiedSince > 0 && ifModifiedSince <= lastModifiedMillis) { |
| 460 |
// not modified, content is not sent - only basic headers and status SC_NOT_MODIFIED |
| 461 |
response.setDateHeader("Expires", expires); |
| 462 |
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); |
| 463 |
is.close(); |
| 464 |
return; |
| 465 |
} |
| 466 |
|
| 467 |
// set the content-type header |
| 468 |
String contentType = getContentType(name); |
| 469 |
if (contentType != null) { |
| 470 |
response.setContentType(contentType); |
| 471 |
} |
| 472 |
|
| 473 |
if (serveStaticBrowserCache) { |
| 474 |
// set heading information for caching static content |
| 475 |
response.setDateHeader("Date", now); |
| 476 |
response.setDateHeader("Expires", expires); |
| 477 |
response.setDateHeader("Retry-After", expires); |
| 478 |
response.setHeader("Cache-Control", "public"); |
| 479 |
response.setDateHeader("Last-Modified", lastModifiedMillis); |
| 480 |
} else { |
| 481 |
response.setHeader("Cache-Control", "no-cache"); |
| 482 |
response.setHeader("Pragma", "no-cache"); |
| 483 |
response.setHeader("Expires", "-1"); |
| 484 |
} |
| 485 |
|
| 486 |
try { |
| 487 |
copy(is, response.getOutputStream()); |
| 488 |
} finally { |
| 489 |
is.close(); |
| 490 |
} |
| 491 |
return; |
| 492 |
} |
| 493 |
} |
| 494 |
} |
| 495 |
|
| 496 |
response.sendError(HttpServletResponse.SC_NOT_FOUND); |
| 497 |
} |
| 498 |
|
| 499 |
/** |
| 500 |
* Determine the content type for the resource name. |
| 501 |
* |
| 502 |
* @param name The resource name |
| 503 |
* @return The mime type |
| 504 |
*/ |
| 505 |
protected String getContentType(String name) { |
| 506 |
// NOT using the code provided activation.jar to avoid adding yet another dependency |
| 507 |
// this is generally OK, since these are the main files we server up |
| 508 |
if (name.endsWith(".js")) { |
| 509 |
return "text/javascript"; |
| 510 |
} else if (name.endsWith(".css")) { |
| 511 |
return "text/css"; |
| 512 |
} else if (name.endsWith(".html")) { |
| 513 |
return "text/html"; |
| 514 |
} else if (name.endsWith(".txt")) { |
| 515 |
return "text/plain"; |
| 516 |
} else if (name.endsWith(".gif")) { |
| 517 |
return "image/gif"; |
| 518 |
} else if (name.endsWith(".jpg") || name.endsWith(".jpeg")) { |
| 519 |
return "image/jpeg"; |
| 520 |
} else if (name.endsWith(".png")) { |
| 521 |
return "image/png"; |
| 522 |
} else { |
| 523 |
return null; |
| 524 |
} |
| 525 |
} |
| 526 |
|
| 527 |
/** |
| 528 |
* Copy bytes from the input stream to the output stream. |
| 529 |
* |
| 530 |
* @param input The input stream |
| 531 |
* @param output The output stream |
| 532 |
* @throws IOException If anything goes wrong |
| 533 |
*/ |
| 534 |
protected void copy(InputStream input, OutputStream output) throws IOException { |
| 535 |
final byte[] buffer = new byte[4096]; |
| 536 |
int n; |
| 537 |
while (-1 != (n = input.read(buffer))) { |
| 538 |
output.write(buffer, 0, n); |
| 539 |
} |
| 540 |
} |
| 541 |
|
| 542 |
/** |
| 543 |
* Look for a static resource in the classpath. |
| 544 |
* |
| 545 |
* @param name The resource name |
| 546 |
* @param packagePrefix The package prefix to use to locate the resource |
| 547 |
* @return The inputstream of the resource |
| 548 |
* @throws IOException If there is a problem locating the resource |
| 549 |
*/ |
| 550 |
protected InputStream findInputStream(String name, String packagePrefix) throws IOException { |
| 551 |
String resourcePath; |
| 552 |
if (packagePrefix.endsWith("/") && name.startsWith("/")) { |
| 553 |
resourcePath = packagePrefix + name.substring(1); |
| 554 |
} else { |
| 555 |
resourcePath = packagePrefix + name; |
| 556 |
} |
| 557 |
|
| 558 |
resourcePath = URLDecoder.decode(resourcePath, encoding); |
| 559 |
|
| 560 |
return ClassLoaderUtil.getResourceAsStream(resourcePath, getClass()); |
| 561 |
} |
| 562 |
} |