View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.application;
20  
21  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
22  import org.apache.myfaces.shared.resource.ResourceHandlerCache;
23  import org.apache.myfaces.shared.resource.ResourceHandlerCache.ResourceValue;
24  import org.apache.myfaces.shared.resource.ResourceHandlerSupport;
25  import org.apache.myfaces.shared.resource.ResourceImpl;
26  import org.apache.myfaces.shared.resource.ResourceLoader;
27  import org.apache.myfaces.shared.resource.ResourceMeta;
28  import org.apache.myfaces.shared.resource.ResourceValidationUtils;
29  import org.apache.myfaces.shared.util.ClassUtils;
30  import org.apache.myfaces.shared.util.ExternalContextUtils;
31  import org.apache.myfaces.shared.util.StringUtils;
32  import org.apache.myfaces.shared.util.WebConfigParamUtils;
33  
34  import javax.faces.application.Resource;
35  import javax.faces.application.ResourceHandler;
36  import javax.faces.application.ResourceWrapper;
37  import javax.faces.context.ExternalContext;
38  import javax.faces.context.FacesContext;
39  import javax.servlet.http.HttpServletResponse;
40  import java.io.IOException;
41  import java.io.InputStream;
42  import java.io.OutputStream;
43  import java.net.URL;
44  import java.util.Locale;
45  import java.util.Map;
46  import java.util.MissingResourceException;
47  import java.util.ResourceBundle;
48  import java.util.logging.Level;
49  import java.util.logging.Logger;
50  
51  /**
52   * DOCUMENT ME!
53   *
54   * @author Simon Lessard (latest modification by $Author: lu4242 $)
55   * 
56   * @version $Revision: 1238687 $ $Date: 2012-01-31 11:36:49 -0500 (Tue, 31 Jan 2012) $
57   */
58  public class ResourceHandlerImpl extends ResourceHandler
59  {
60  
61      private static final String IS_RESOURCE_REQUEST = "org.apache.myfaces.IS_RESOURCE_REQUEST";
62  
63      private ResourceHandlerSupport _resourceHandlerSupport;
64  
65      private ResourceHandlerCache _resourceHandlerCache;
66  
67      //private static final Log log = LogFactory.getLog(ResourceHandlerImpl.class);
68      private static final Logger log = Logger.getLogger(ResourceHandlerImpl.class.getName());
69  
70      private static final int _BUFFER_SIZE = 2048;
71      
72      /**
73       * Allow slash in the library name of a Resource. 
74       */
75      @JSFWebConfigParam(since="2.1.6, 2.0.12", defaultValue="false", 
76              expectedValues="true, false", group="resources")
77      public static final String INIT_PARAM_STRICT_JSF_2_ALLOW_SLASH_LIBRARY_NAME = 
78              "org.apache.myfaces.STRICT_JSF_2_ALLOW_SLASH_LIBRARY_NAME";
79      public static final boolean INIT_PARAM_STRICT_JSF_2_ALLOW_SLASH_LIBRARY_NAME_DEFAULT = false;
80      
81      private Boolean _allowSlashLibraryName;
82  
83      @Override
84      public Resource createResource(String resourceName)
85      {
86          return createResource(resourceName, null);
87      }
88  
89      @Override
90      public Resource createResource(String resourceName, String libraryName)
91      {
92          return createResource(resourceName, libraryName, null);
93      }
94  
95      @Override
96      public Resource createResource(String resourceName, String libraryName,
97              String contentType)
98      {
99          Resource resource = null;
100         
101         if (!ResourceValidationUtils.isValidResourceName(resourceName))
102         {
103             return null;
104         }
105         if (libraryName != null && !ResourceValidationUtils.isValidLibraryName(
106                 libraryName, isAllowSlashesLibraryName()))
107         {
108             return null;
109         }
110         
111         if (contentType == null)
112         {
113             //Resolve contentType using ExternalContext.getMimeType
114             contentType = FacesContext.getCurrentInstance().getExternalContext().getMimeType(resourceName);
115         }
116 
117         final String localePrefix = getLocalePrefixForLocateResource();
118 
119         // check cache
120         if(getResourceLoaderCache().containsResource(resourceName, libraryName, contentType, localePrefix))
121         {
122             ResourceValue resourceValue = getResourceLoaderCache().getResource(
123                     resourceName, libraryName, contentType, localePrefix);
124             
125             resource = new ResourceImpl(resourceValue.getResourceMeta(), resourceValue.getResourceLoader(),
126                     getResourceHandlerSupport(), contentType);
127         }
128         else
129         {
130             for (ResourceLoader loader : getResourceHandlerSupport().getResourceLoaders())
131             {
132                 ResourceMeta resourceMeta = deriveResourceMeta(loader, resourceName, libraryName, localePrefix);
133     
134                 if (resourceMeta != null)
135                 {
136                     resource = new ResourceImpl(resourceMeta, loader, getResourceHandlerSupport(), contentType);
137 
138                     // cache it
139                     getResourceLoaderCache().putResource(resourceName, libraryName, contentType,
140                             localePrefix, resourceMeta, loader);
141                     break;
142                 }
143             }
144         }
145         
146         return resource;
147     }
148 
149     /**
150      * This method try to create a ResourceMeta for a specific resource
151      * loader. If no library, or resource is found, just return null,
152      * so the algorithm in createResource can continue checking with the 
153      * next registered ResourceLoader. 
154      */
155     protected ResourceMeta deriveResourceMeta(ResourceLoader resourceLoader,
156             String resourceName, String libraryName, String localePrefix)
157     {
158         String resourceVersion = null;
159         String libraryVersion = null;
160         ResourceMeta resourceId = null;
161         
162         //1. Try to locate resource in a localized path
163         if (localePrefix != null)
164         {
165             if (null != libraryName)
166             {
167                 String pathToLib = localePrefix + '/' + libraryName;
168                 libraryVersion = resourceLoader.getLibraryVersion(pathToLib);
169 
170                 if (null != libraryVersion)
171                 {
172                     String pathToResource = localePrefix + '/'
173                             + libraryName + '/' + libraryVersion + '/'
174                             + resourceName;
175                     resourceVersion = resourceLoader
176                             .getResourceVersion(pathToResource);
177                 }
178                 else
179                 {
180                     String pathToResource = localePrefix + '/'
181                             + libraryName + '/' + resourceName;
182                     resourceVersion = resourceLoader
183                             .getResourceVersion(pathToResource);
184                 }
185 
186                 if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
187                 {
188                     resourceId = resourceLoader.createResourceMeta(localePrefix, libraryName,
189                             libraryVersion, resourceName, resourceVersion);
190                 }
191             }
192             else
193             {
194                 resourceVersion = resourceLoader
195                         .getResourceVersion(localePrefix + '/'+ resourceName);
196                 if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
197                 {               
198                     resourceId = resourceLoader.createResourceMeta(localePrefix, null, null,
199                             resourceName, resourceVersion);
200                 }
201             }
202 
203             if (resourceId != null)
204             {
205                 URL url = resourceLoader.getResourceURL(resourceId);
206                 if (url == null)
207                 {
208                     resourceId = null;
209                 }
210             }            
211         }
212         
213         //2. Try to localize resource in a non localized path
214         if (resourceId == null)
215         {
216             if (null != libraryName)
217             {
218                 libraryVersion = resourceLoader.getLibraryVersion(libraryName);
219 
220                 if (null != libraryVersion)
221                 {
222                     String pathToResource = (libraryName + '/' + libraryVersion
223                             + '/' + resourceName);
224                     resourceVersion = resourceLoader
225                             .getResourceVersion(pathToResource);
226                 }
227                 else
228                 {
229                     String pathToResource = (libraryName + '/'
230                             + resourceName);
231                     resourceVersion = resourceLoader
232                             .getResourceVersion(pathToResource);
233                 }
234 
235                 if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
236                 {               
237                     resourceId = resourceLoader.createResourceMeta(null, libraryName,
238                             libraryVersion, resourceName, resourceVersion);
239                 }
240             }
241             else
242             {
243                 resourceVersion = resourceLoader
244                         .getResourceVersion(resourceName);
245                 
246                 if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
247                 {               
248                     resourceId = resourceLoader.createResourceMeta(null, null, null,
249                             resourceName, resourceVersion);
250                 }
251             }
252 
253             if (resourceId != null)
254             {
255                 URL url = resourceLoader.getResourceURL(resourceId);
256                 if (url == null)
257                 {
258                     resourceId = null;
259                 }
260             }            
261         }
262         
263         return resourceId;
264     }
265 
266     @Override
267     public String getRendererTypeForResourceName(String resourceName)
268     {
269         if (resourceName.endsWith(".js"))
270         {
271             return "javax.faces.resource.Script";
272         }
273         else if (resourceName.endsWith(".css"))
274         {
275             return "javax.faces.resource.Stylesheet";
276         }
277         return null;
278     }
279 
280     /**
281      *  Handle the resource request, writing in the output. 
282      *  
283      *  This method implements an algorithm semantically identical to 
284      *  the one described on the javadoc of ResourceHandler.handleResourceRequest 
285      */
286     @Override
287     public void handleResourceRequest(FacesContext facesContext) throws IOException
288     {
289         //try
290         //{
291             String resourceBasePath = getResourceHandlerSupport()
292                     .calculateResourceBasePath(facesContext);
293     
294             if (resourceBasePath == null)
295             {
296                 // No base name could be calculated, so no further
297                 //advance could be done here. HttpServletResponse.SC_NOT_FOUND
298                 //cannot be returned since we cannot extract the 
299                 //resource base name
300                 return;
301             }
302     
303             // We neet to get an instance of HttpServletResponse, but sometimes
304             // the response object is wrapped by several instances of 
305             // ServletResponseWrapper (like ResponseSwitch).
306             // Since we are handling a resource, we can expect to get an 
307             // HttpServletResponse.
308             Object response = facesContext.getExternalContext().getResponse();
309             HttpServletResponse httpServletResponse = ExternalContextUtils.getHttpServletResponse(response);
310             if (httpServletResponse == null)
311             {
312                 throw new IllegalStateException("Could not obtain an instance of HttpServletResponse.");
313             }
314     
315             if (isResourceIdentifierExcluded(facesContext, resourceBasePath))
316             {
317                 httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
318                 return;
319             }
320     
321             String resourceName = null;
322             if (resourceBasePath.startsWith(ResourceHandler.RESOURCE_IDENTIFIER))
323             {
324                 resourceName = resourceBasePath
325                         .substring(ResourceHandler.RESOURCE_IDENTIFIER.length() + 1);
326                 
327                 if (resourceBasePath != null && !ResourceValidationUtils.isValidResourceName(resourceName))
328                 {
329                     httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
330                     return;
331                 }
332             }
333             else
334             {
335                 //Does not have the conditions for be a resource call
336                 httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
337                 return;
338             }
339     
340             String libraryName = facesContext.getExternalContext()
341                     .getRequestParameterMap().get("ln");
342     
343             if (libraryName != null && !ResourceValidationUtils.isValidLibraryName(
344                     libraryName, isAllowSlashesLibraryName()))
345             {
346                 httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
347                 return;
348             }
349             
350             Resource resource = null;
351             if (libraryName != null)
352             {
353                 //log.info("libraryName=" + libraryName);
354                 resource = facesContext.getApplication().getResourceHandler().createResource(resourceName, libraryName);
355             }
356             else
357             {
358                 resource = facesContext.getApplication().getResourceHandler().createResource(resourceName);
359             }
360     
361             if (resource == null)
362             {
363                 httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
364                 return;
365             }
366     
367             if (!resource.userAgentNeedsUpdate(facesContext))
368             {
369                 httpServletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
370                 return;
371             }
372     
373             httpServletResponse.setContentType(_getContentType(resource, facesContext.getExternalContext()));
374     
375             Map<String, String> headers = resource.getResponseHeaders();
376     
377             for (Map.Entry<String, String> entry : headers.entrySet())
378             {
379                 httpServletResponse.setHeader(entry.getKey(), entry.getValue());
380             }
381     
382             //serve up the bytes (taken from trinidad ResourceServlet)
383             try
384             {
385                 InputStream in = resource.getInputStream();
386                 OutputStream out = httpServletResponse.getOutputStream();
387                 byte[] buffer = new byte[_BUFFER_SIZE];
388     
389                 try
390                 {
391                     int count = pipeBytes(in, out, buffer);
392                     //set the content lenght
393                     httpServletResponse.setContentLength(count);
394                 }
395                 finally
396                 {
397                     try
398                     {
399                         in.close();
400                     }
401                     finally
402                     {
403                         out.close();
404                     }
405                 }
406             }
407             catch (IOException e)
408             {
409                 //TODO: Log using a localized message (which one?)
410                 if (log.isLoggable(Level.SEVERE))
411                 {
412                     log.severe("Error trying to load resource " + resourceName
413                             + " with library " + libraryName + " :"
414                             + e.getMessage());
415                 }
416                 httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
417             }
418         //}
419         //catch (Throwable ex)
420         //{
421             // handle the Throwable accordingly. Maybe generate an error page.
422             // FIXME we are creating a html error page for a non html request here
423             // shouln't we do something better? -=Jakob Korherr=-
424             //ErrorPageWriter.handleThrowable(facesContext, ex);
425         //}
426     }
427 
428     /**
429      * Reads the specified input stream into the provided byte array storage and
430      * writes it to the output stream.
431      */
432     private static int pipeBytes(InputStream in, OutputStream out, byte[] buffer)
433             throws IOException
434     {
435         int count = 0;
436         int length;
437 
438         while ((length = (in.read(buffer))) >= 0)
439         {
440             out.write(buffer, 0, length);
441             count += length;
442         }
443         return count;
444     }
445 
446     @Override
447     public boolean isResourceRequest(FacesContext facesContext)
448     {
449         // Since this method could be called many times we save it
450         //on request map so the first time is calculated it remains
451         //alive until the end of the request
452         Boolean value = (Boolean) facesContext.getAttributes().get(IS_RESOURCE_REQUEST);
453 
454         if (value != null && value)
455         {
456             //return the saved value
457             return value;
458         }
459         else
460         {
461             String resourceBasePath = getResourceHandlerSupport()
462                     .calculateResourceBasePath(facesContext);
463 
464             if (resourceBasePath != null
465                     && resourceBasePath.startsWith(ResourceHandler.RESOURCE_IDENTIFIER))
466             {
467                 facesContext.getAttributes().put(IS_RESOURCE_REQUEST, Boolean.TRUE);
468                 return true;
469             }
470             else
471             {
472                 facesContext.getAttributes().put(IS_RESOURCE_REQUEST, Boolean.FALSE);
473                 return false;
474             }
475         }
476     }
477 
478     protected String getLocalePrefixForLocateResource()
479     {
480         String localePrefix = null;
481         FacesContext context = FacesContext.getCurrentInstance();
482         boolean isResourceRequest = context.getApplication().getResourceHandler().isResourceRequest(context);
483 
484         if (isResourceRequest)
485         {
486             localePrefix = context.getExternalContext().getRequestParameterMap().get("loc");
487             
488             if (localePrefix != null)
489             {
490                 if (!ResourceValidationUtils.isValidLocalePrefix(localePrefix))
491                 {
492                     return null;
493                 }
494                 return localePrefix;
495             }
496         }
497         
498         String bundleName = context.getApplication().getMessageBundle();
499 
500         if (null != bundleName)
501         {
502             Locale locale = null;
503             
504             if (isResourceRequest || context.getViewRoot() == null)
505             {
506                 locale = context.getApplication().getViewHandler()
507                                 .calculateLocale(context);
508             }
509             else
510             {
511                 locale = context.getViewRoot().getLocale();
512             }
513 
514             try
515             {
516                 ResourceBundle bundle = ResourceBundle
517                         .getBundle(bundleName, locale, ClassUtils.getContextClassLoader());
518 
519                 if (bundle != null)
520                 {
521                     localePrefix = bundle.getString(ResourceHandler.LOCALE_PREFIX);
522                 }
523             }
524             catch (MissingResourceException e)
525             {
526                 // Ignore it and return null
527             }
528         }
529         return localePrefix;
530     }
531 
532     protected boolean isResourceIdentifierExcluded(FacesContext context,
533             String resourceIdentifier)
534     {
535         String value = context.getExternalContext().getInitParameter(
536                 RESOURCE_EXCLUDES_PARAM_NAME);
537         if (value == null)
538         {
539             value = RESOURCE_EXCLUDES_DEFAULT_VALUE;
540         }
541         //TODO: optimize this code
542         String[] extensions = StringUtils.splitShortString(value, ' ');
543         for (int i = 0; i < extensions.length; i++)
544         {
545             if (resourceIdentifier.endsWith(extensions[i]))
546             {
547                 return true;
548             }
549         }
550         return false;
551     }
552 
553     /**
554      * Check if a library exists or not. This is done delegating
555      * to each ResourceLoader used, because each one has a different
556      * prefix and way to load resources.
557      * 
558      */
559     @Override
560     public boolean libraryExists(String libraryName)
561     {
562         String localePrefix = getLocalePrefixForLocateResource();
563 
564         String pathToLib = null;
565         
566         if (libraryName != null && !ResourceValidationUtils.isValidLibraryName(
567                 libraryName, isAllowSlashesLibraryName()))
568         {
569             return false;
570         }
571         
572         if (localePrefix != null)
573         {
574             //Check with locale
575             pathToLib = localePrefix + '/' + libraryName;
576             
577             for (ResourceLoader loader : getResourceHandlerSupport()
578                     .getResourceLoaders())
579             {
580                 if (loader.libraryExists(pathToLib))
581                 {
582                     return true;
583                 }
584             }            
585         }
586 
587         //Check without locale
588         for (ResourceLoader loader : getResourceHandlerSupport()
589                 .getResourceLoaders())
590         {
591             if (loader.libraryExists(libraryName))
592             {
593                 return true;
594             }
595         }
596 
597         return false;
598     }
599 
600     /**
601      * @param resourceHandlerSupport
602      *            the resourceHandlerSupport to set
603      */
604     public void setResourceHandlerSupport(
605             ResourceHandlerSupport resourceHandlerSupport)
606     {
607         _resourceHandlerSupport = resourceHandlerSupport;
608     }
609 
610     /**
611      * @return the resourceHandlerSupport
612      */
613     protected ResourceHandlerSupport getResourceHandlerSupport()
614     {
615         if (_resourceHandlerSupport == null)
616         {
617             _resourceHandlerSupport = new DefaultResourceHandlerSupport();
618         }
619         return _resourceHandlerSupport;
620     }
621 
622     private ResourceHandlerCache getResourceLoaderCache()
623     {
624         if (_resourceHandlerCache == null)
625         {
626             _resourceHandlerCache = new ResourceHandlerCache();
627         }
628         return _resourceHandlerCache;
629     }
630 
631     private String _getContentType(Resource resource, ExternalContext externalContext)
632     {
633         String contentType = resource.getContentType();
634 
635         // the resource does not provide a content-type --> determine it via mime-type
636         if (contentType == null || contentType.length() == 0)
637         {
638             String resourceName = getWrappedResourceName(resource);
639 
640             if (resourceName != null)
641             {
642                 contentType = externalContext.getMimeType(resourceName);
643             }
644         }
645 
646         return contentType;
647     }
648 
649     /**
650      * Recursively unwarp the resource until we find the real resourceName
651      * This is needed because the JSF2 specced ResourceWrapper doesn't override
652      * the getResourceName() method :(
653      * @param resource
654      * @return the first non-null resourceName or <code>null</code> if none set
655      */
656     private String getWrappedResourceName(Resource resource)
657     {
658         String resourceName = resource.getResourceName();
659         if (resourceName != null)
660         {
661             return resourceName;
662         }
663 
664         if (resource instanceof ResourceWrapper)
665         {
666             return getWrappedResourceName(((ResourceWrapper) resource).getWrapped());
667         }
668 
669         return null;
670     }
671     
672     protected boolean isAllowSlashesLibraryName()
673     {
674         if (_allowSlashLibraryName == null)
675         {
676             _allowSlashLibraryName = WebConfigParamUtils.getBooleanInitParameter(
677                     FacesContext.getCurrentInstance().getExternalContext(), 
678                     INIT_PARAM_STRICT_JSF_2_ALLOW_SLASH_LIBRARY_NAME,
679                     INIT_PARAM_STRICT_JSF_2_ALLOW_SLASH_LIBRARY_NAME_DEFAULT);
680         }
681         return _allowSlashLibraryName;
682     }
683 
684 }