1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.myfaces.trinidad.webapp;
20
21 import java.io.BufferedReader;
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.InputStreamReader;
26 import java.io.InterruptedIOException;
27 import java.io.OutputStream;
28 import java.io.Reader;
29 import java.lang.reflect.Constructor;
30 import java.lang.reflect.InvocationTargetException;
31 import java.net.SocketException;
32 import java.net.URL;
33 import java.net.URLConnection;
34 import java.util.HashMap;
35 import java.util.Map;
36
37 import javax.faces.FacesException;
38 import javax.faces.FactoryFinder;
39 import javax.faces.application.ProjectStage;
40 import javax.faces.context.FacesContext;
41 import javax.faces.context.FacesContextFactory;
42 import javax.faces.event.PhaseListener;
43 import javax.faces.lifecycle.Lifecycle;
44 import javax.naming.Context;
45 import javax.naming.InitialContext;
46 import javax.naming.NamingException;
47 import javax.servlet.ServletConfig;
48 import javax.servlet.ServletContext;
49 import javax.servlet.ServletException;
50 import javax.servlet.ServletRequest;
51 import javax.servlet.ServletResponse;
52 import javax.servlet.http.HttpServlet;
53 import javax.servlet.http.HttpServletRequest;
54 import javax.servlet.http.HttpServletResponse;
55
56 import org.apache.myfaces.trinidad.config.Configurator;
57 import org.apache.myfaces.trinidad.logging.TrinidadLogger;
58 import org.apache.myfaces.trinidad.resource.CachingResourceLoader;
59 import org.apache.myfaces.trinidad.resource.DirectoryResourceLoader;
60 import org.apache.myfaces.trinidad.resource.ResourceLoader;
61 import org.apache.myfaces.trinidad.resource.ServletContextResourceLoader;
62 import org.apache.myfaces.trinidad.util.URLUtils;
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 public class ResourceServlet extends HttpServlet
83 {
84
85
86
87 private static final long serialVersionUID = 4547362994406585148L;
88
89
90
91
92 @Override
93 public void destroy()
94 {
95 _loaders = null;
96 _facesContextFactory = null;
97 _lifecycle = null;
98
99 super.destroy();
100 }
101
102
103
104
105 @Override
106 public void init(
107 ServletConfig config
108 ) throws ServletException
109 {
110 super.init(config);
111
112
113 try
114 {
115 _facesContextFactory = (FacesContextFactory)
116 FactoryFinder.getFactory
117 (FactoryFinder.FACES_CONTEXT_FACTORY);
118 }
119 catch (FacesException e)
120 {
121 Throwable rootCause = e.getCause();
122 if (rootCause == null)
123 {
124 throw e;
125 }
126 else
127 {
128 throw new ServletException(e.getMessage(), rootCause);
129 }
130 }
131
132
133 _lifecycle = new _ResourceLifecycle();
134 _initDebug(config);
135 _loaders = new HashMap<String, ResourceLoader>();
136 }
137
138 @Override
139 public void service(
140 ServletRequest request,
141 ServletResponse response
142 ) throws ServletException, IOException
143 {
144 boolean hasFacesContext = false;
145 FacesContext context = FacesContext.getCurrentInstance();
146
147
148
149
150 if (context != null)
151 {
152 hasFacesContext = true;
153 }
154 else
155 {
156 Configurator.disableConfiguratorServices(request);
157
158
159
160
161 context = _facesContextFactory.getFacesContext(getServletContext(), request, response, _lifecycle);
162 }
163
164 try
165 {
166 super.service(request, response);
167 }
168 catch (ServletException e)
169 {
170 _LOG.severe(e);
171 throw e;
172 }
173 catch (IOException e)
174 {
175 if (!_canIgnore(e))
176 _LOG.severe(e);
177 throw e;
178 }
179 finally
180 {
181 if (!hasFacesContext)
182 context.release();
183 }
184 }
185
186
187
188
189 @Override
190 protected void doGet(
191 HttpServletRequest request,
192 HttpServletResponse response
193 ) throws ServletException, IOException
194 {
195 ResourceLoader loader = _getResourceLoader(request);
196 String resourcePath = getResourcePath(request);
197 URL url = loader.getResource(resourcePath);
198
199
200 if (url == null)
201 {
202
203 _LOG.warning("URL for resource not found.\n resourcePath: {0}\n loader class name: {1}\n request.pathTranslated: {2}\n request.requestURL: {3}",
204 new Object[] { resourcePath,
205 loader,
206 request.getPathTranslated(),
207 request.getRequestURL() });
208 response.sendError(HttpServletResponse.SC_NOT_FOUND);
209 return;
210 }
211
212
213 URLConnection connection = url.openConnection();
214 connection.setDoInput(true);
215 connection.setDoOutput(false);
216
217 _setHeaders(connection, response, loader);
218
219 InputStream in = connection.getInputStream();
220 OutputStream out = response.getOutputStream();
221 byte[] buffer = new byte[_BUFFER_SIZE];
222
223 try
224 {
225 _pipeBytes(in, out, buffer);
226 }
227 finally
228 {
229 try
230 {
231 in.close();
232 }
233 finally
234 {
235 out.close();
236 }
237 }
238 }
239
240
241
242
243 @Override
244 protected long getLastModified(
245 HttpServletRequest request)
246 {
247 try
248 {
249 ResourceLoader loader = _getResourceLoader(request);
250 String resourcePath = getResourcePath(request);
251 URL url = loader.getResource(resourcePath);
252
253 if (url == null)
254 return super.getLastModified(request);
255
256 return URLUtils.getLastModified(url);
257 }
258 catch (IOException e)
259 {
260
261
262 return super.getLastModified(request);
263 }
264 }
265
266
267
268
269
270
271
272
273 protected String getResourcePath(
274 HttpServletRequest request)
275 {
276 return request.getServletPath() + request.getPathInfo();
277 }
278
279
280
281
282 private ResourceLoader _getResourceLoader(
283 HttpServletRequest request)
284 {
285 final String servletPath = request.getServletPath();
286 ResourceLoader loader = _loaders.get(servletPath);
287
288 if (loader == null)
289 {
290 try
291 {
292 String key = "META-INF/servlets/resources" +
293 servletPath +
294 ".resources";
295 ClassLoader cl = Thread.currentThread().getContextClassLoader();
296 URL url = cl.getResource(key);
297
298 if (url != null)
299 {
300 Reader r = new InputStreamReader(url.openStream());
301 BufferedReader br = new BufferedReader(r);
302 try
303 {
304 String className = br.readLine();
305 if (className != null)
306 {
307 className = className.trim();
308 Class<?> clazz = cl.loadClass(className);
309 try
310 {
311 Constructor<?> decorator = clazz.getConstructor(_DECORATOR_SIGNATURE);
312 ServletContext context = getServletContext();
313 File tempdir = (File)
314 context.getAttribute("javax.servlet.context.tempdir");
315 ResourceLoader delegate = new DirectoryResourceLoader(tempdir);
316 loader = (ResourceLoader)
317 decorator.newInstance(new Object[]{delegate});
318 }
319 catch (InvocationTargetException e)
320 {
321
322 loader = (ResourceLoader) clazz.newInstance();
323 }
324 catch (NoSuchMethodException e)
325 {
326
327 loader = (ResourceLoader) clazz.newInstance();
328 }
329 }
330 }
331 finally
332 {
333 br.close();
334 }
335 }
336 else
337 {
338
339 _LOG.warning("Unable to find ResourceLoader for ResourceServlet" +
340 " at servlet path:{0}" +
341 "\nCause: Could not find resource:{1}",
342 new Object[] {servletPath, key});
343 loader = new ServletContextResourceLoader(getServletContext())
344 {
345 @Override
346 public URL getResource(
347 String path) throws IOException
348 {
349 return super.getResource(path);
350 }
351 };
352 }
353
354
355 if (!_debug && loader.isCachable())
356 {
357 loader = new CachingResourceLoader(loader);
358 }
359 }
360 catch (IllegalAccessException e)
361 {
362 loader = ResourceLoader.getNullResourceLoader();
363 }
364 catch (InstantiationException e)
365 {
366 loader = ResourceLoader.getNullResourceLoader();
367 }
368 catch (ClassNotFoundException e)
369 {
370 loader = ResourceLoader.getNullResourceLoader();
371 }
372 catch (IOException e)
373 {
374 loader = ResourceLoader.getNullResourceLoader();
375 }
376
377 _loaders.put(servletPath, loader);
378 }
379
380 return loader;
381 }
382
383
384
385
386
387 private static void _pipeBytes(
388 InputStream in,
389 OutputStream out,
390 byte[] buffer
391 ) throws IOException
392 {
393 int length;
394
395 while ((length = (in.read(buffer))) >= 0)
396 {
397 out.write(buffer, 0, length);
398 }
399 }
400
401
402
403
404 private void _initDebug(
405 ServletConfig config
406 )
407 {
408 String debug = config.getInitParameter(DEBUG_INIT_PARAM);
409 if (debug == null)
410 {
411
412
413 debug = config.getServletContext().getInitParameter(DEBUG_INIT_PARAM);
414 }
415
416
417
418 ProjectStage currentStage = _getFacesProjectStage(config.getServletContext());
419
420 if (debug != null)
421 {
422 _debug = "true".equalsIgnoreCase(debug);
423 }
424 else
425 {
426
427
428
429
430 _debug = !(ProjectStage.Production.equals(currentStage));
431 }
432
433 if (_debug)
434 {
435
436
437 if (ProjectStage.Production.equals(currentStage))
438 {
439 _LOG.warning("RESOURCESERVLET_IN_DEBUG_MODE",DEBUG_INIT_PARAM);
440 }
441 else
442 {
443 _LOG.info("RESOURCESERVLET_IN_DEBUG_MODE",DEBUG_INIT_PARAM);
444 }
445 }
446 }
447
448
449
450
451
452
453
454
455
456
457 private ProjectStage _getFacesProjectStage(ServletContext servletContext)
458 {
459 if (_projectStage == null)
460 {
461 String stageName = null;
462
463
464 try
465 {
466 Context ctx = new InitialContext();
467 Object temp = ctx.lookup(ProjectStage.PROJECT_STAGE_JNDI_NAME);
468 if (temp != null)
469 {
470 if (temp instanceof String)
471 {
472 stageName = (String) temp;
473 }
474 else
475 {
476 if (_LOG.isSevere())
477 {
478 _LOG.severe("Invalid JNDI lookup for key " + ProjectStage.PROJECT_STAGE_JNDI_NAME);
479 }
480 }
481 }
482 }
483 catch (NamingException e)
484 {
485
486 }
487
488
489
490
491
492 if (stageName == null)
493 {
494 stageName = servletContext.getInitParameter(ProjectStage.PROJECT_STAGE_PARAM_NAME);
495 }
496
497
498 if (stageName != null)
499 {
500
501
502
503
504 try
505 {
506 _projectStage = ProjectStage.valueOf(stageName);
507 return _projectStage;
508 }
509 catch (IllegalArgumentException e)
510 {
511 _LOG.severe("Couldn't discover the current project stage", e);
512 }
513 }
514 else
515 {
516 if (_LOG.isInfo())
517 {
518 _LOG.info("Couldn't discover the current project stage, using " + ProjectStage.Production);
519 }
520 }
521
522
523
524
525
526 _projectStage = ProjectStage.Production;
527 }
528
529 return _projectStage;
530 }
531
532
533
534
535
536 private void _setHeaders(
537 URLConnection connection,
538 HttpServletResponse response,
539 ResourceLoader loader)
540 {
541 String resourcePath;
542 URL url;
543 String contentType = ResourceLoader.getContentType(loader, connection);
544
545 if (contentType == null || "content/unknown".equals(contentType))
546 {
547 url = connection.getURL();
548 resourcePath = url.getPath();
549
550
551 if (resourcePath.endsWith(".css"))
552 contentType = "text/css";
553 else if (resourcePath.endsWith(".js"))
554 contentType = "application/x-javascript";
555 else if (resourcePath.endsWith(".cur") || resourcePath.endsWith(".ico"))
556 contentType = "image/vnd.microsoft.icon";
557 else
558 contentType = getServletContext().getMimeType(resourcePath);
559
560
561
562 if (contentType == null)
563 {
564 _LOG.warning("ResourceServlet._setHeaders(): " +
565 "Content type for {0} is NULL!\n" +
566 "Cause: Unknown file extension",
567 resourcePath);
568 }
569 }
570
571 if (contentType != null)
572 {
573 response.setContentType(contentType);
574 int contentLength = connection.getContentLength();
575
576 if (contentLength >= 0)
577 response.setContentLength(contentLength);
578 }
579
580 long lastModified;
581 try
582 {
583 lastModified = URLUtils.getLastModified(connection);
584 }
585 catch (IOException exception)
586 {
587 lastModified = -1;
588 }
589
590 if (lastModified >= 0)
591 response.setDateHeader("Last-Modified", lastModified);
592
593
594 if (!_debug)
595 {
596
597
598
599
600
601 response.setHeader("Cache-Control", "Public");
602
603
604 long currentTime = System.currentTimeMillis();
605
606 response.setDateHeader("Expires", currentTime + ONE_YEAR_MILLIS);
607 }
608 }
609
610 private static boolean _canIgnore(Throwable t)
611 {
612 if (t instanceof InterruptedIOException)
613 {
614
615 return true;
616 }
617 else if (t instanceof SocketException)
618 {
619
620
621
622
623 return true;
624 }
625 else if (t instanceof IOException)
626 {
627 String message = t.getMessage();
628
629
630 if ((message != null) &&
631 ((message.indexOf("Broken pipe") >= 0) ||
632 (message.indexOf("abort") >= 0)))
633 return true;
634 }
635 return false;
636 }
637
638 static private class _ResourceLifecycle extends Lifecycle
639 {
640 @Override
641 public void execute(FacesContext p0) throws FacesException
642 {
643 }
644
645 @Override
646 public PhaseListener[] getPhaseListeners()
647 {
648 return null;
649 }
650
651 @Override
652 public void removePhaseListener(PhaseListener p0)
653 {
654 }
655
656 @Override
657 public void render(FacesContext p0) throws FacesException
658 {
659 }
660
661 @Override
662 public void addPhaseListener(PhaseListener p0)
663 {
664 }
665 }
666
667
668
669
670
671 public static final String DEBUG_INIT_PARAM =
672 "org.apache.myfaces.trinidad.resource.DEBUG";
673
674
675
676
677 public static final long ONE_YEAR_MILLIS = 31363200000L;
678
679 private static final Class[] _DECORATOR_SIGNATURE =
680 new Class[]{ResourceLoader.class};
681
682 private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(ResourceServlet.class);
683
684
685 private static final int _BUFFER_SIZE = 2048;
686
687 private boolean _debug;
688 private Map<String, ResourceLoader> _loaders;
689 private FacesContextFactory _facesContextFactory;
690 private Lifecycle _lifecycle;
691 private ProjectStage _projectStage;
692 }