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.util.List;
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  import java.util.regex.Pattern;
51  import org.apache.myfaces.shared.resource.ContractResource;
52  import org.apache.myfaces.shared.resource.ContractResourceLoader;
53  import org.apache.myfaces.shared.resource.ResourceCachedInfo;
54  
55  /**
56   * DOCUMENT ME!
57   *
58   * @author Simon Lessard (latest modification by $Author: lu4242 $)
59   * 
60   * @version $Revision: 1669627 $ $Date: 2015-03-27 17:20:33 +0000 (Fri, 27 Mar 2015) $
61   */
62  public class ResourceHandlerImpl extends ResourceHandler
63  {
64  
65      private static final String IS_RESOURCE_REQUEST = "org.apache.myfaces.IS_RESOURCE_REQUEST";
66  
67      private ResourceHandlerSupport _resourceHandlerSupport;
68  
69      private ResourceHandlerCache _resourceHandlerCache;
70  
71      //private static final Log log = LogFactory.getLog(ResourceHandlerImpl.class);
72      private static final Logger log = Logger.getLogger(ResourceHandlerImpl.class.getName());
73  
74      /**
75       * Allow slash in the library name of a Resource. 
76       */
77      @JSFWebConfigParam(since="2.1.6, 2.0.12", defaultValue="false", 
78              expectedValues="true, false", group="resources")
79      public static final String INIT_PARAM_STRICT_JSF_2_ALLOW_SLASH_LIBRARY_NAME = 
80              "org.apache.myfaces.STRICT_JSF_2_ALLOW_SLASH_LIBRARY_NAME";
81      public static final boolean INIT_PARAM_STRICT_JSF_2_ALLOW_SLASH_LIBRARY_NAME_DEFAULT = false;
82      
83      /**
84       * Define the default buffer size that is used between Resource.getInputStream() and 
85       * httpServletResponse.getOutputStream() when rendering resources using the default
86       * ResourceHandler.
87       */
88      @JSFWebConfigParam(since="2.1.10, 2.0.16", defaultValue="2048", group="resources")
89      public static final String INIT_PARAM_RESOURCE_BUFFER_SIZE = "org.apache.myfaces.RESOURCE_BUFFER_SIZE";
90      public static final int INIT_PARAM_RESOURCE_BUFFER_SIZE_DEFAULT = 2048;
91      
92      public static final Pattern LIBRARY_VERSION_CHECKER = Pattern.compile("\\p{Digit}+(_\\p{Digit}*)*");
93      public static final Pattern RESOURCE_VERSION_CHECKER = Pattern.compile("\\p{Digit}+(_\\p{Digit}*)*\\..*");    
94      
95      private Boolean _allowSlashLibraryName;
96      private int _resourceBufferSize = -1;
97      
98      private String[] _excludedResourceExtensions;
99  
100     @Override
101     public Resource createResource(String resourceName)
102     {
103         return createResource(resourceName, null);
104     }
105 
106     @Override
107     public Resource createResource(String resourceName, String libraryName)
108     {
109         return createResource(resourceName, libraryName, null);
110     }
111 
112     @Override
113     public Resource createResource(String resourceName, String libraryName,
114             String contentType)
115     {
116         Resource resource = null;
117         
118         if (resourceName.charAt(0) == '/')
119         {
120             // If resourceName starts with '/', remove that character because it
121             // does not have any meaning (with and without should point to the 
122             // same resource).
123             resourceName = resourceName.substring(1);
124         }        
125         if (!ResourceValidationUtils.isValidResourceName(resourceName))
126         {
127             return null;
128         }
129         if (libraryName != null && !ResourceValidationUtils.isValidLibraryName(
130                 libraryName, isAllowSlashesLibraryName()))
131         {
132             return null;
133         }
134         FacesContext facesContext = FacesContext.getCurrentInstance();
135         if (contentType == null)
136         {
137             //Resolve contentType using ExternalContext.getMimeType
138             contentType = facesContext.getExternalContext().getMimeType(resourceName);
139         }
140 
141         final String localePrefix = getLocalePrefixForLocateResource(facesContext);
142         final List<String> contracts = facesContext.getResourceLibraryContracts(); 
143         String contractPreferred = getContractNameForLocateResource(facesContext);
144         ResourceValue resourceValue = null;
145 
146         // Check cache:
147         //
148         // Contracts are on top of everything, because it is a concept that defines
149         // resources in a application scope concept. It means all resources in
150         // /resources or /META-INF/resources can be overriden using a contract. Note
151         // it also means resources under /META-INF/flows can also be overriden using
152         // a contract.
153         
154         // Check first the preferred contract if any. If not found, try the remaining
155         // contracts and finally if not found try to found a resource without a 
156         // contract name.
157         if (contractPreferred != null)
158         {
159             resourceValue = getResourceLoaderCache().getResource(
160                     resourceName, libraryName, contentType, localePrefix, contractPreferred);
161         }
162         if (resourceValue == null && !contracts.isEmpty())
163         {
164             // Try to get resource but try with a contract name
165             for (String contract : contracts)
166             {
167                 resourceValue = getResourceLoaderCache().getResource(
168                     resourceName, libraryName, contentType, localePrefix, contract);
169                 if (resourceValue != null)
170                 {
171                     break;
172                 }
173             }
174         }
175         // Only if no contract preferred try without it.
176         if (resourceValue == null)
177         {
178             // Try to get resource without contract name
179             resourceValue = getResourceLoaderCache().getResource(resourceName, libraryName, contentType, localePrefix);
180         }
181         
182         if(resourceValue != null)
183         {
184             resource = new ResourceImpl(resourceValue.getResourceMeta(), resourceValue.getResourceLoader(),
185                     getResourceHandlerSupport(), contentType, 
186                     resourceValue.getCachedInfo() != null ? resourceValue.getCachedInfo().getURL() : null, 
187                     resourceValue.getCachedInfo() != null ? resourceValue.getCachedInfo().getRequestPath() : null);
188         }
189         else
190         {
191             boolean resolved = false;
192             // Try preferred contract first
193             if (contractPreferred != null)
194             {
195                 for (ContractResourceLoader loader : getResourceHandlerSupport().getContractResourceLoaders())
196                 {
197                     ResourceMeta resourceMeta = deriveResourceMeta(loader, resourceName, libraryName, 
198                         localePrefix, contractPreferred);
199                     if (resourceMeta != null)
200                     {
201                         resource = new ResourceImpl(resourceMeta, loader, 
202                             getResourceHandlerSupport(), contentType);
203 
204                         // cache it
205                         getResourceLoaderCache().putResource(resourceName, libraryName, contentType,
206                                 localePrefix, contractPreferred, resourceMeta, loader, 
207                                 new ResourceCachedInfo(resource.getURL(), resource.getRequestPath()));
208                         resolved = true;
209                         break;
210                     }
211                 }
212             }
213             if (!resolved && !contracts.isEmpty())
214             {
215                 for (ContractResourceLoader loader : 
216                     getResourceHandlerSupport().getContractResourceLoaders())
217                 {
218                     for (String contract : contracts)
219                     {
220                         ResourceMeta resourceMeta = deriveResourceMeta(
221                             loader, resourceName, libraryName, 
222                             localePrefix, contract);
223                         if (resourceMeta != null)
224                         {
225                             resource = new ResourceImpl(resourceMeta, loader, 
226                                 getResourceHandlerSupport(), contentType);
227 
228                             // cache it
229                             getResourceLoaderCache().putResource(
230                                     resourceName, libraryName, contentType,
231                                     localePrefix, contract, resourceMeta, loader,
232                                     new ResourceCachedInfo(resource.getURL(), resource.getRequestPath()));
233                             resolved = true;
234                             break;
235                         }
236                     }
237                 }
238             }
239             if (!resolved)
240             {
241                 for (ResourceLoader loader : getResourceHandlerSupport().getResourceLoaders())
242                 {
243                     ResourceMeta resourceMeta = deriveResourceMeta(
244                         loader, resourceName, libraryName, localePrefix);
245 
246                     if (resourceMeta != null)
247                     {
248                         resource = new ResourceImpl(
249                             resourceMeta, loader, getResourceHandlerSupport(), contentType);
250 
251                         // cache it
252                         getResourceLoaderCache().putResource(resourceName, libraryName, contentType,
253                                 localePrefix, null, resourceMeta, loader, 
254                                 new ResourceCachedInfo(resource.getURL(), resource.getRequestPath()));
255                         break;
256                     }
257                 }
258             }
259         }
260         return resource;
261     }
262 
263     protected ResourceMeta deriveResourceMeta(ContractResourceLoader resourceLoader,
264             String resourceName, String libraryName, String localePrefix, String contractName)
265     {
266         String resourceVersion = null;
267         String libraryVersion = null;
268         ResourceMeta resourceId = null;
269         
270         //1. Try to locate resource in a localized path
271         if (localePrefix != null)
272         {
273             if (null != libraryName)
274             {
275                 String pathToLib = localePrefix + '/' + libraryName;
276                 libraryVersion = resourceLoader.getLibraryVersion(pathToLib, contractName);
277 
278                 if (null != libraryVersion)
279                 {
280                     String pathToResource = localePrefix + '/'
281                             + libraryName + '/' + libraryVersion + '/'
282                             + resourceName;
283                     resourceVersion = resourceLoader
284                             .getResourceVersion(pathToResource, contractName);
285                 }
286                 else
287                 {
288                     String pathToResource = localePrefix + '/'
289                             + libraryName + '/' + resourceName;
290                     resourceVersion = resourceLoader
291                             .getResourceVersion(pathToResource, contractName);
292                 }
293 
294                 if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
295                 {
296                     resourceId = resourceLoader.createResourceMeta(localePrefix, libraryName,
297                             libraryVersion, resourceName, resourceVersion, contractName);
298                 }
299             }
300             else
301             {
302                 resourceVersion = resourceLoader
303                         .getResourceVersion(localePrefix + '/'+ resourceName, contractName);
304                 if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
305                 {               
306                     resourceId = resourceLoader.createResourceMeta(localePrefix, null, null,
307                             resourceName, resourceVersion, contractName);
308                 }
309             }
310 
311             if (resourceId != null && !resourceLoader.resourceExists(resourceId))
312             {
313                 resourceId = null;
314             }            
315         }
316         
317         //2. Try to localize resource in a non localized path
318         if (resourceId == null)
319         {
320             if (null != libraryName)
321             {
322                 libraryVersion = resourceLoader.getLibraryVersion(libraryName, contractName);
323 
324                 if (null != libraryVersion)
325                 {
326                     String pathToResource = (libraryName + '/' + libraryVersion
327                             + '/' + resourceName);
328                     resourceVersion = resourceLoader
329                             .getResourceVersion(pathToResource, contractName);
330                 }
331                 else
332                 {
333                     String pathToResource = (libraryName + '/'
334                             + resourceName);
335                     resourceVersion = resourceLoader
336                             .getResourceVersion(pathToResource, contractName);
337                 }
338 
339                 if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
340                 {               
341                     resourceId = resourceLoader.createResourceMeta(null, libraryName,
342                             libraryVersion, resourceName, resourceVersion, contractName);
343                 }
344             }
345             else
346             {
347                 resourceVersion = resourceLoader
348                         .getResourceVersion(resourceName, contractName);
349                 
350                 if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
351                 {               
352                     resourceId = resourceLoader.createResourceMeta(null, null, null,
353                             resourceName, resourceVersion, contractName);
354                 }
355             }
356 
357             if (resourceId != null && !resourceLoader.resourceExists(resourceId))
358             {
359                 resourceId = null;
360             }            
361         }
362         
363         return resourceId;
364     }
365     
366     /**
367      * This method try to create a ResourceMeta for a specific resource
368      * loader. If no library, or resource is found, just return null,
369      * so the algorithm in createResource can continue checking with the 
370      * next registered ResourceLoader. 
371      */
372     protected ResourceMeta deriveResourceMeta(ResourceLoader resourceLoader,
373             String resourceName, String libraryName, String localePrefix)
374     {
375         String resourceVersion = null;
376         String libraryVersion = null;
377         ResourceMeta resourceId = null;
378         
379         //1. Try to locate resource in a localized path
380         if (localePrefix != null)
381         {
382             if (null != libraryName)
383             {
384                 String pathToLib = localePrefix + '/' + libraryName;
385                 libraryVersion = resourceLoader.getLibraryVersion(pathToLib);
386 
387                 if (null != libraryVersion)
388                 {
389                     String pathToResource = localePrefix + '/'
390                             + libraryName + '/' + libraryVersion + '/'
391                             + resourceName;
392                     resourceVersion = resourceLoader
393                             .getResourceVersion(pathToResource);
394                 }
395                 else
396                 {
397                     String pathToResource = localePrefix + '/'
398                             + libraryName + '/' + resourceName;
399                     resourceVersion = resourceLoader
400                             .getResourceVersion(pathToResource);
401                 }
402 
403                 if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
404                 {
405                     resourceId = resourceLoader.createResourceMeta(localePrefix, libraryName,
406                             libraryVersion, resourceName, resourceVersion);
407                 }
408             }
409             else
410             {
411                 resourceVersion = resourceLoader
412                         .getResourceVersion(localePrefix + '/'+ resourceName);
413                 if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
414                 {               
415                     resourceId = resourceLoader.createResourceMeta(localePrefix, null, null,
416                             resourceName, resourceVersion);
417                 }
418             }
419 
420             if (resourceId != null && !resourceLoader.resourceExists(resourceId))
421             {
422                 resourceId = null;
423             }            
424         }
425         
426         //2. Try to localize resource in a non localized path
427         if (resourceId == null)
428         {
429             if (null != libraryName)
430             {
431                 libraryVersion = resourceLoader.getLibraryVersion(libraryName);
432 
433                 if (null != libraryVersion)
434                 {
435                     String pathToResource = (libraryName + '/' + libraryVersion
436                             + '/' + resourceName);
437                     resourceVersion = resourceLoader
438                             .getResourceVersion(pathToResource);
439                 }
440                 else
441                 {
442                     String pathToResource = (libraryName + '/'
443                             + resourceName);
444                     resourceVersion = resourceLoader
445                             .getResourceVersion(pathToResource);
446                 }
447 
448                 if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
449                 {               
450                     resourceId = resourceLoader.createResourceMeta(null, libraryName,
451                             libraryVersion, resourceName, resourceVersion);
452                 }
453             }
454             else
455             {
456                 resourceVersion = resourceLoader
457                         .getResourceVersion(resourceName);
458                 
459                 if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
460                 {               
461                     resourceId = resourceLoader.createResourceMeta(null, null, null,
462                             resourceName, resourceVersion);
463                 }
464             }
465 
466             if (resourceId != null && !resourceLoader.resourceExists(resourceId))
467             {
468                 resourceId = null;
469             }            
470         }
471         
472         return resourceId;
473     }
474 
475     @Override
476     public String getRendererTypeForResourceName(String resourceName)
477     {
478         if (resourceName.endsWith(".js"))
479         {
480             return "javax.faces.resource.Script";
481         }
482         else if (resourceName.endsWith(".css"))
483         {
484             return "javax.faces.resource.Stylesheet";
485         }
486         return null;
487     }
488 
489     /**
490      *  Handle the resource request, writing in the output. 
491      *  
492      *  This method implements an algorithm semantically identical to 
493      *  the one described on the javadoc of ResourceHandler.handleResourceRequest 
494      */
495     @Override
496     public void handleResourceRequest(FacesContext facesContext) throws IOException
497     {
498         //try
499         //{
500             String resourceBasePath = getResourceHandlerSupport()
501                     .calculateResourceBasePath(facesContext);
502     
503             if (resourceBasePath == null)
504             {
505                 // No base name could be calculated, so no further
506                 //advance could be done here. HttpServletResponse.SC_NOT_FOUND
507                 //cannot be returned since we cannot extract the 
508                 //resource base name
509                 return;
510             }
511     
512             // We neet to get an instance of HttpServletResponse, but sometimes
513             // the response object is wrapped by several instances of 
514             // ServletResponseWrapper (like ResponseSwitch).
515             // Since we are handling a resource, we can expect to get an 
516             // HttpServletResponse.
517             ExternalContext extContext = facesContext.getExternalContext();
518             Object response = extContext.getResponse();
519             HttpServletResponse httpServletResponse = ExternalContextUtils.getHttpServletResponse(response);
520             if (httpServletResponse == null)
521             {
522                 throw new IllegalStateException("Could not obtain an instance of HttpServletResponse.");
523             }
524     
525             if (isResourceIdentifierExcluded(facesContext, resourceBasePath))
526             {
527                 httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
528                 return;
529             }
530     
531             String resourceName = null;
532             if (resourceBasePath.startsWith(ResourceHandler.RESOURCE_IDENTIFIER))
533             {
534                 resourceName = resourceBasePath
535                         .substring(ResourceHandler.RESOURCE_IDENTIFIER.length() + 1);
536                 
537                 if (resourceBasePath != null && !ResourceValidationUtils.isValidResourceName(resourceName))
538                 {
539                     httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
540                     return;
541                 }
542             }
543             else
544             {
545                 //Does not have the conditions for be a resource call
546                 httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
547                 return;
548             }
549     
550             String libraryName = facesContext.getExternalContext()
551                     .getRequestParameterMap().get("ln");
552     
553             if (libraryName != null && !ResourceValidationUtils.isValidLibraryName(
554                     libraryName, isAllowSlashesLibraryName()))
555             {
556                 httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
557                 return;
558             }
559             
560             Resource resource = null;
561             if (libraryName != null)
562             {
563                 //log.info("libraryName=" + libraryName);
564                 resource = facesContext.getApplication().getResourceHandler().createResource(resourceName, libraryName);
565             }
566             else
567             {
568                 resource = facesContext.getApplication().getResourceHandler().createResource(resourceName);
569             }
570     
571             if (resource == null)
572             {
573                 httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
574                 return;
575             }
576     
577             if (!resource.userAgentNeedsUpdate(facesContext))
578             {
579                 httpServletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
580                 return;
581             }
582     
583             httpServletResponse.setContentType(_getContentType(resource, facesContext.getExternalContext()));
584     
585             Map<String, String> headers = resource.getResponseHeaders();
586     
587             for (Map.Entry<String, String> entry : headers.entrySet())
588             {
589                 httpServletResponse.setHeader(entry.getKey(), entry.getValue());
590             }
591     
592             // Sets the preferred buffer size for the body of the response
593             extContext.setResponseBufferSize(this.getResourceBufferSize());
594             
595             //serve up the bytes (taken from trinidad ResourceServlet)
596             try
597             {
598                 InputStream in = resource.getInputStream();
599                 OutputStream out = httpServletResponse.getOutputStream();
600                 //byte[] buffer = new byte[_BUFFER_SIZE];
601                 byte[] buffer = new byte[this.getResourceBufferSize()];
602     
603                 try
604                 {
605                     int count = pipeBytes(in, out, buffer);
606                     //set the content lenght
607                     if (!httpServletResponse.isCommitted())
608                     {
609                         httpServletResponse.setContentLength(count);
610                     }
611                 }
612                 finally
613                 {
614                     try
615                     {
616                         in.close();
617                     }
618                     finally
619                     {
620                         out.close();
621                     }
622                 }
623             }
624             catch (IOException e)
625             {
626                 //TODO: Log using a localized message (which one?)
627                 if (log.isLoggable(Level.SEVERE))
628                 {
629                     log.log(Level.SEVERE,"Error trying to load resource " + resourceName
630                             + " with library " + libraryName + " :"
631                             + e.getMessage(), e);
632                 }
633                 httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
634             }
635         //}
636         //catch (Throwable ex)
637         //{
638             // handle the Throwable accordingly. Maybe generate an error page.
639             // FIXME we are creating a html error page for a non html request here
640             // shouln't we do something better? -=Jakob Korherr=-
641             //ErrorPageWriter.handleThrowable(facesContext, ex);
642         //}
643     }
644 
645     /**
646      * Reads the specified input stream into the provided byte array storage and
647      * writes it to the output stream.
648      */
649     private static int pipeBytes(InputStream in, OutputStream out, byte[] buffer)
650             throws IOException
651     {
652         int count = 0;
653         int length;
654 
655         while ((length = (in.read(buffer))) >= 0)
656         {
657             out.write(buffer, 0, length);
658             count += length;
659         }
660         return count;
661     }
662 
663     @Override
664     public boolean isResourceRequest(FacesContext facesContext)
665     {
666         // Since this method could be called many times we save it
667         // on request map so the first time is calculated it remains
668         // alive until the end of the request
669         Boolean value = (Boolean) facesContext.getAttributes().get(IS_RESOURCE_REQUEST);
670 
671         if (value == null)
672         {
673             String resourceBasePath = getResourceHandlerSupport()
674                     .calculateResourceBasePath(facesContext);
675 
676             value = resourceBasePath != null
677                     && resourceBasePath.startsWith(ResourceHandler.RESOURCE_IDENTIFIER);
678             facesContext.getAttributes().put(IS_RESOURCE_REQUEST, value);
679         }
680         return value;
681     }
682 
683     protected String getLocalePrefixForLocateResource()
684     {
685         return getLocalePrefixForLocateResource(FacesContext.getCurrentInstance());
686     }
687 
688     protected String getLocalePrefixForLocateResource(FacesContext context)
689     {
690         String localePrefix = null;
691         boolean isResourceRequest = context.getApplication().getResourceHandler().isResourceRequest(context);
692 
693         if (isResourceRequest)
694         {
695             localePrefix = context.getExternalContext().getRequestParameterMap().get("loc");
696             
697             if (localePrefix != null)
698             {
699                 if (!ResourceValidationUtils.isValidLocalePrefix(localePrefix))
700                 {
701                     return null;
702                 }
703                 return localePrefix;
704             }
705         }
706         
707         String bundleName = context.getApplication().getMessageBundle();
708 
709         if (null != bundleName)
710         {
711             Locale locale = null;
712             
713             if (isResourceRequest || context.getViewRoot() == null)
714             {
715                 locale = context.getApplication().getViewHandler()
716                                 .calculateLocale(context);
717             }
718             else
719             {
720                 locale = context.getViewRoot().getLocale();
721             }
722 
723             try
724             {
725                 ResourceBundle bundle = ResourceBundle
726                         .getBundle(bundleName, locale, ClassUtils.getContextClassLoader());
727 
728                 if (bundle != null)
729                 {
730                     localePrefix = bundle.getString(ResourceHandler.LOCALE_PREFIX);
731                 }
732             }
733             catch (MissingResourceException e)
734             {
735                 // Ignore it and return null
736             }
737         }
738         return localePrefix;
739     }
740     
741     protected String getContractNameForLocateResource(FacesContext context)
742     {
743         String contractName = null;
744         boolean isResourceRequest = context.getApplication().getResourceHandler().isResourceRequest(context);
745 
746         if (isResourceRequest)
747         {
748             contractName = context.getExternalContext().getRequestParameterMap().get("con");
749         }
750         
751         // Check if the contract has been injected.
752         if (contractName == null)
753         {
754             contractName = (String) context.getAttributes().get(ContractResource.CONTRACT_SELECTED);
755         }
756         
757         //Validate
758         if (contractName != null &&
759             !ResourceValidationUtils.isValidContractName(contractName))
760         {
761             return null;
762         }
763         return contractName;
764     }
765 
766     protected boolean isResourceIdentifierExcluded(FacesContext context, String resourceIdentifier)
767     {
768         if (_excludedResourceExtensions == null)
769         {
770             String value = WebConfigParamUtils.getStringInitParameter(context.getExternalContext(),
771                             RESOURCE_EXCLUDES_PARAM_NAME,
772                             RESOURCE_EXCLUDES_DEFAULT_VALUE);
773             
774             _excludedResourceExtensions = StringUtils.splitShortString(value, ' ');
775         }
776         
777         for (int i = 0; i < _excludedResourceExtensions.length; i++)
778         {
779             if (resourceIdentifier.endsWith(_excludedResourceExtensions[i]))
780             {
781                 return true;
782             }
783         }
784         return false;
785     }
786 
787     /**
788      * Check if a library exists or not. This is done delegating
789      * to each ResourceLoader used, because each one has a different
790      * prefix and way to load resources.
791      * 
792      */
793     @Override
794     public boolean libraryExists(String libraryName)
795     {
796         FacesContext facesContext = FacesContext.getCurrentInstance();
797         String localePrefix = getLocalePrefixForLocateResource(facesContext);
798         final List<String> contracts = facesContext.getResourceLibraryContracts(); 
799 
800         String pathToLib = null;
801         Boolean libraryFound = null;
802         if (libraryName != null && !ResourceValidationUtils.isValidLibraryName(
803                 libraryName, isAllowSlashesLibraryName()))
804         {
805             return false;
806         }
807         
808         if (localePrefix != null)
809         {
810             //Check with locale
811             pathToLib = localePrefix + '/' + libraryName;
812 
813             libraryFound = getResourceLoaderCache().libraryExists(pathToLib);
814             if (libraryFound != null)
815             {
816                 return libraryFound.booleanValue();
817             }
818         }
819         libraryFound = getResourceLoaderCache().libraryExists(libraryName);
820         if (libraryFound != null)
821         {
822             return libraryFound.booleanValue();
823         }
824         
825         if (localePrefix != null)
826         {
827             if (!contracts.isEmpty())
828             {
829                 for (String contract : contracts)
830                 {
831                     for (ContractResourceLoader loader : getResourceHandlerSupport()
832                             .getContractResourceLoaders())
833                     {
834                         if (loader.libraryExists(pathToLib, contract))
835                         {
836                             getResourceLoaderCache().confirmLibraryExists(pathToLib);
837                             return true;
838                         }
839                     }
840                 }
841             }
842             
843             for (ResourceLoader loader : getResourceHandlerSupport()
844                     .getResourceLoaders())
845             {
846                 if (loader.libraryExists(pathToLib))
847                 {
848                     getResourceLoaderCache().confirmLibraryExists(pathToLib);
849                     return true;
850                 }
851             }            
852         }
853 
854         //Check without locale
855         if (!contracts.isEmpty())
856         {
857             for (String contract : contracts)
858             {
859                 for (ContractResourceLoader loader : getResourceHandlerSupport()
860                         .getContractResourceLoaders())
861                 {
862                     if (loader.libraryExists(libraryName, contract))
863                     {
864                         getResourceLoaderCache().confirmLibraryExists(libraryName);
865                         return true;
866                     }
867                 }
868             }
869         }
870 
871         for (ResourceLoader loader : getResourceHandlerSupport()
872                 .getResourceLoaders())
873         {
874             if (loader.libraryExists(libraryName))
875             {
876                 getResourceLoaderCache().confirmLibraryExists(libraryName);
877                 return true;
878             }
879         }
880 
881         if (localePrefix != null)
882         {
883             //Check with locale
884             getResourceLoaderCache().confirmLibraryNotExists(pathToLib);
885         }
886         else
887         {
888             getResourceLoaderCache().confirmLibraryNotExists(libraryName);
889         }
890         return false;
891     }
892 
893     /**
894      * @param resourceHandlerSupport
895      *            the resourceHandlerSupport to set
896      */
897     public void setResourceHandlerSupport(
898             ResourceHandlerSupport resourceHandlerSupport)
899     {
900         _resourceHandlerSupport = resourceHandlerSupport;
901     }
902 
903     /**
904      * @return the resourceHandlerSupport
905      */
906     protected ResourceHandlerSupport getResourceHandlerSupport()
907     {
908         if (_resourceHandlerSupport == null)
909         {
910             _resourceHandlerSupport = new DefaultResourceHandlerSupport();
911         }
912         return _resourceHandlerSupport;
913     }
914 
915     private ResourceHandlerCache getResourceLoaderCache()
916     {
917         if (_resourceHandlerCache == null)
918         {
919             _resourceHandlerCache = new ResourceHandlerCache();
920         }
921         return _resourceHandlerCache;
922     }
923 
924     private String _getContentType(Resource resource, ExternalContext externalContext)
925     {
926         String contentType = resource.getContentType();
927 
928         // the resource does not provide a content-type --> determine it via mime-type
929         if (contentType == null || contentType.length() == 0)
930         {
931             String resourceName = getWrappedResourceName(resource);
932 
933             if (resourceName != null)
934             {
935                 contentType = externalContext.getMimeType(resourceName);
936             }
937         }
938 
939         return contentType;
940     }
941 
942     /**
943      * Recursively unwarp the resource until we find the real resourceName
944      * This is needed because the JSF2 specced ResourceWrapper doesn't override
945      * the getResourceName() method :(
946      * @param resource
947      * @return the first non-null resourceName or <code>null</code> if none set
948      */
949     private String getWrappedResourceName(Resource resource)
950     {
951         String resourceName = resource.getResourceName();
952         if (resourceName != null)
953         {
954             return resourceName;
955         }
956 
957         if (resource instanceof ResourceWrapper)
958         {
959             return getWrappedResourceName(((ResourceWrapper) resource).getWrapped());
960         }
961 
962         return null;
963     }
964     
965     protected boolean isAllowSlashesLibraryName()
966     {
967         if (_allowSlashLibraryName == null)
968         {
969             _allowSlashLibraryName = WebConfigParamUtils.getBooleanInitParameter(
970                     FacesContext.getCurrentInstance().getExternalContext(), 
971                     INIT_PARAM_STRICT_JSF_2_ALLOW_SLASH_LIBRARY_NAME,
972                     INIT_PARAM_STRICT_JSF_2_ALLOW_SLASH_LIBRARY_NAME_DEFAULT);
973         }
974         return _allowSlashLibraryName;
975     }
976 
977     protected int getResourceBufferSize()
978     {
979         if (_resourceBufferSize == -1)
980         {
981             _resourceBufferSize = WebConfigParamUtils.getIntegerInitParameter(
982                 FacesContext.getCurrentInstance().getExternalContext(),
983                 INIT_PARAM_RESOURCE_BUFFER_SIZE,
984                 INIT_PARAM_RESOURCE_BUFFER_SIZE_DEFAULT);
985         }
986         return _resourceBufferSize;
987     }
988 
989     @Override
990     public Resource createResourceFromId(String resourceId)
991     {
992         Resource resource = null;
993 
994         if (resourceId == null)
995         {
996             throw new NullPointerException();
997         }
998         
999         // Later in deriveResourceMeta the resourceId is decomposed and
1000         // its elements validated properly.
1001         if (!ResourceValidationUtils.isValidResourceId(resourceId))
1002         {
1003             return null;
1004         }
1005         
1006         FacesContext facesContext = FacesContext.getCurrentInstance();
1007         final List<String> contracts = facesContext.getResourceLibraryContracts(); 
1008         String contractPreferred = getContractNameForLocateResource(facesContext);
1009         ResourceValue resourceValue = null;
1010         
1011         // Check cache:
1012         //
1013         // Contracts are on top of everything, because it is a concept that defines
1014         // resources in a application scope concept. It means all resources in
1015         // /resources or /META-INF/resources can be overriden using a contract. Note
1016         // it also means resources under /META-INF/flows can also be overriden using
1017         // a contract.
1018         if (contractPreferred != null)
1019         {
1020             resourceValue = getResourceLoaderCache().getResource(
1021                     resourceId, contractPreferred);
1022         }
1023         if (resourceValue == null && !contracts.isEmpty())
1024         {
1025             // Try to get resource but try with a contract name
1026             for (String contract : contracts)
1027             {
1028                 resourceValue = getResourceLoaderCache().getResource(resourceId, contract);
1029                 if (resourceValue != null)
1030                 {
1031                     break;
1032                 }
1033             }
1034         }
1035         if (resourceValue == null)
1036         {
1037             // Try to get resource without contract name
1038             resourceValue = getResourceLoaderCache().getResource(resourceId);
1039         }
1040         
1041         if(resourceValue != null)
1042         {        
1043             //Resolve contentType using ExternalContext.getMimeType
1044             String contentType = facesContext.getExternalContext().getMimeType(
1045                 resourceValue.getResourceMeta().getResourceName());
1046 
1047             resource = new ResourceImpl(resourceValue.getResourceMeta(), resourceValue.getResourceLoader(),
1048                     getResourceHandlerSupport(), contentType,
1049                     resourceValue.getCachedInfo() != null ? resourceValue.getCachedInfo().getURL() : null, 
1050                     resourceValue.getCachedInfo() != null ? resourceValue.getCachedInfo().getRequestPath() : null);
1051         }
1052         else
1053         {
1054             boolean resolved = false;
1055             if (contractPreferred != null)
1056             {
1057                 for (ContractResourceLoader loader : getResourceHandlerSupport().getContractResourceLoaders())
1058                 {
1059                     ResourceMeta resourceMeta = deriveResourceMeta(
1060                         facesContext, loader, resourceId, contractPreferred);
1061                     if (resourceMeta != null)
1062                     {
1063                         String contentType = facesContext.getExternalContext().getMimeType(
1064                             resourceMeta.getResourceName());
1065                         
1066                         resource = new ResourceImpl(resourceMeta, loader, 
1067                             getResourceHandlerSupport(), contentType);
1068 
1069                         // cache it
1070                         getResourceLoaderCache().putResource(resourceId, resourceMeta, loader, 
1071                             new ResourceCachedInfo(resource.getURL(), resource.getRequestPath()));
1072                         
1073                         resolved = true;
1074                         break;
1075                     }
1076                 }
1077             }
1078             if (!resolved && !contracts.isEmpty())
1079             {
1080                 for (ContractResourceLoader loader : 
1081                         getResourceHandlerSupport().getContractResourceLoaders())
1082                 {
1083                     for (String contract : contracts)
1084                     {
1085                         ResourceMeta resourceMeta = deriveResourceMeta(
1086                             facesContext, loader, resourceId, contract);
1087                         if (resourceMeta != null)
1088                         {
1089                             String contentType = facesContext.getExternalContext().getMimeType(
1090                                 resourceMeta.getResourceName());
1091 
1092                             resource = new ResourceImpl(resourceMeta, loader, 
1093                                 getResourceHandlerSupport(), contentType);
1094 
1095                             // cache it
1096                             getResourceLoaderCache().putResource(resourceId, resourceMeta, loader, 
1097                                 new ResourceCachedInfo(resource.getURL(), resource.getRequestPath()));
1098 
1099                             resolved = true;
1100                             break;
1101                         }
1102                     }
1103                 }
1104             }
1105             if (!resolved)
1106             {
1107                 for (ResourceLoader loader : getResourceHandlerSupport().getResourceLoaders())
1108                 {
1109                     ResourceMeta resourceMeta = deriveResourceMeta(facesContext, loader, resourceId);
1110 
1111                     if (resourceMeta != null)
1112                     {
1113                         String contentType = facesContext.getExternalContext().getMimeType(
1114                             resourceMeta.getResourceName());
1115 
1116                         resource = new ResourceImpl(resourceMeta, loader, getResourceHandlerSupport(), contentType);
1117 
1118                         // cache it
1119                         getResourceLoaderCache().putResource(resourceId, resourceMeta, loader, 
1120                             new ResourceCachedInfo(resource.getURL(), resource.getRequestPath()));
1121                         break;
1122                     }
1123                 }
1124             }
1125         }
1126         return resource;
1127     }
1128 
1129     protected ResourceMeta deriveResourceMeta(FacesContext context, ResourceLoader resourceLoader,
1130             String resourceId)
1131     {
1132         ResourceMeta resourceMeta = null;
1133         String token = null;
1134         String localePrefix = null;
1135         String libraryName = null;
1136         String libraryVersion = null;
1137         String resourceName = null;
1138         String resourceVersion = null;
1139         
1140         // Check if resource exists. It avoids additional 
1141         // checks and it can be done very quickly because the 
1142         // loader always uses the resourceId structure to
1143         // organize resources. But decompose the resourceId is
1144         // even faster.
1145         //if (resourceLoader.resourceIdExists(resourceId))
1146         //{
1147         int lastSlash = resourceId.lastIndexOf('/');
1148         if (lastSlash < 0)
1149         {
1150             //no slashes, so it is just a plain resource.
1151             resourceName = resourceId;
1152         }
1153         else
1154         {
1155             token = resourceId.substring(lastSlash+1);
1156             if (RESOURCE_VERSION_CHECKER.matcher(token).matches())
1157             {
1158                 int secondLastSlash = resourceId.lastIndexOf('/', lastSlash-1);
1159                 if (secondLastSlash < 0)
1160                 {
1161                     secondLastSlash = 0;
1162                 }
1163 
1164                 String rnToken = resourceId.substring(secondLastSlash+1, lastSlash);
1165                 int lastPoint = rnToken.lastIndexOf('.');
1166                 // lastPoint < 0 means it does not match, the token is not a resource version
1167                 if (lastPoint >= 0)
1168                 {
1169                     String ext = rnToken.substring(lastPoint);
1170                     if (token.endsWith(ext))
1171                     {
1172                         //It match a versioned resource
1173                         resourceVersion = token.substring(0,token.length()-ext.length());
1174                     }
1175                 }
1176             }
1177 
1178             // 1. Extract the library path and locale prefix if necessary
1179             int start = 0;
1180             int firstSlash = resourceId.indexOf('/');
1181 
1182             // At least one slash, check if the start is locale prefix.
1183             String bundleName = context.getApplication().getMessageBundle();
1184             //If no bundle set, it can't be localePrefix
1185             if (null != bundleName)
1186             {
1187                 token = resourceId.substring(start, firstSlash);
1188                 //Try to derive a locale object
1189                 Locale locale = _LocaleUtils.deriveLocale(token);
1190 
1191                 // If the locale was derived and it is available, 
1192                 // assume that portion of the resourceId it as a locale prefix.
1193                 if (locale != null && _LocaleUtils.isAvailableLocale(locale))
1194                 {
1195                     localePrefix = token;
1196                     start = firstSlash+1;
1197                 }
1198             }
1199 
1200             //Check slash again from start
1201             firstSlash = resourceId.indexOf('/', start);
1202             if (firstSlash < 0)
1203             {
1204                 //no slashes.
1205                 resourceName = resourceId.substring(start);
1206             }
1207             else
1208             {
1209                 //check libraryName
1210                 token = resourceId.substring(start, firstSlash);
1211                 int minResourceNameSlash = (resourceVersion != null) ?
1212                     resourceId.lastIndexOf('/', lastSlash-1) : lastSlash;
1213                 //if (resourceLoader.libraryExists(token))
1214                 if (start < minResourceNameSlash)
1215                 {
1216                     libraryName = token;
1217                     start = firstSlash+1;
1218 
1219                     //Now that libraryName exists, check libraryVersion
1220                     firstSlash = resourceId.indexOf('/', start);
1221                     if (firstSlash >= 0)
1222                     {
1223                         token = resourceId.substring(start, firstSlash);
1224                         if (LIBRARY_VERSION_CHECKER.matcher(token).matches())
1225                         {
1226                             libraryVersion = token;
1227                             start = firstSlash+1;
1228                         }
1229                     }
1230                 }
1231 
1232                 firstSlash = resourceId.indexOf('/', start);
1233                 if (firstSlash < 0)
1234                 {
1235                     //no slashes.
1236                     resourceName = resourceId.substring(start);
1237                 }
1238                 else
1239                 {
1240                     // Check resource version. 
1241                     if (resourceVersion != null)
1242                     {
1243                         resourceName = resourceId.substring(start,lastSlash);
1244                     }
1245                     else
1246                     {
1247                         //no resource version, assume the remaining to be resource name
1248                         resourceName = resourceId.substring(start);
1249                     }
1250                 }
1251             }
1252         }
1253 
1254         //Check libraryName and resourceName
1255         if (resourceName == null)
1256         {
1257             return null;
1258         }
1259         if (!ResourceValidationUtils.isValidResourceName(resourceName))
1260         {
1261             return null;
1262         }
1263 
1264         if (libraryName != null && !ResourceValidationUtils.isValidLibraryName(
1265                 libraryName, isAllowSlashesLibraryName()))
1266         {
1267             return null;
1268         }
1269 
1270         // If some variable is "" set it as null.
1271         if (localePrefix != null && localePrefix.length() == 0)
1272         {
1273             localePrefix = null;
1274         }
1275         if (libraryName != null && libraryName.length() == 0)
1276         {
1277             libraryName = null;
1278         }
1279         if (libraryVersion != null && libraryVersion.length() == 0)
1280         {
1281             libraryVersion = null;
1282         }
1283         if (resourceName != null && resourceName.length() == 0)
1284         {
1285             resourceName = null;
1286         }
1287         if (resourceVersion != null && resourceVersion.length() == 0)
1288         {
1289             resourceVersion = null;
1290         }
1291 
1292         resourceMeta = resourceLoader.createResourceMeta(
1293             localePrefix, libraryName, libraryVersion, resourceName, resourceVersion);
1294 
1295         if (resourceMeta != null &&
1296             !resourceLoader.resourceExists(resourceMeta))
1297         {
1298             resourceMeta = null;
1299         }
1300         //}
1301         return resourceMeta;
1302     }
1303     
1304     protected ResourceMeta deriveResourceMeta(FacesContext context, ContractResourceLoader resourceLoader,
1305             String resourceId, String contractName)
1306     {
1307         ResourceMeta resourceMeta = null;
1308         String token = null;
1309         String localePrefix = null;
1310         String libraryName = null;
1311         String libraryVersion = null;
1312         String resourceName = null;
1313         String resourceVersion = null;
1314         
1315         // Check if resource exists. It avoids additional 
1316         // checks and it can be done very quickly because the 
1317         // loader always uses the resourceId structure to
1318         // organize resources. But decompose the resourceId is
1319         // even faster.
1320         //if (resourceLoader.resourceIdExists(resourceId))
1321         //{
1322         int lastSlash = resourceId.lastIndexOf('/');
1323         if (lastSlash < 0)
1324         {
1325             //no slashes, so it is just a plain resource.
1326             resourceName = resourceId;
1327         }
1328         else
1329         {
1330             token = resourceId.substring(lastSlash+1);
1331             if (RESOURCE_VERSION_CHECKER.matcher(token).matches())
1332             {
1333                 int secondLastSlash = resourceId.lastIndexOf('/', lastSlash-1);
1334                 if (secondLastSlash < 0)
1335                 {
1336                     secondLastSlash = 0;
1337                 }
1338 
1339                 String rnToken = resourceId.substring(secondLastSlash+1, lastSlash);
1340                 int lastPoint = rnToken.lastIndexOf('.');
1341                 // lastPoint < 0 means it does not match, the token is not a resource version
1342                 if (lastPoint >= 0)
1343                 {
1344                     String ext = rnToken.substring(lastPoint);
1345                     if (token.endsWith(ext))
1346                     {
1347                         //It match a versioned resource
1348                         resourceVersion = token.substring(0,token.length()-ext.length());
1349                     }
1350                 }
1351             }
1352 
1353             // 1. Extract the library path and locale prefix if necessary
1354             int start = 0;
1355             int firstSlash = resourceId.indexOf('/');
1356 
1357             // At least one slash, check if the start is locale prefix.
1358             String bundleName = context.getApplication().getMessageBundle();
1359             //If no bundle set, it can't be localePrefix
1360             if (null != bundleName)
1361             {
1362                 token = resourceId.substring(start, firstSlash);
1363                 //Try to derive a locale object
1364                 Locale locale = _LocaleUtils.deriveLocale(token);
1365 
1366                 // If the locale was derived and it is available, 
1367                 // assume that portion of the resourceId it as a locale prefix.
1368                 if (locale != null && _LocaleUtils.isAvailableLocale(locale))
1369                 {
1370                     localePrefix = token;
1371                     start = firstSlash+1;
1372                 }
1373             }
1374 
1375             //Check slash again from start
1376             firstSlash = resourceId.indexOf('/', start);
1377             if (firstSlash < 0)
1378             {
1379                 //no slashes.
1380                 resourceName = resourceId.substring(start);
1381             }
1382             else
1383             {
1384                 //check libraryName
1385                 token = resourceId.substring(start, firstSlash);
1386                 int minResourceNameSlash = (resourceVersion != null) ?
1387                     resourceId.lastIndexOf('/', lastSlash-1) : lastSlash;
1388                 //if (resourceLoader.libraryExists(token))
1389                 if (start < minResourceNameSlash)
1390                 {
1391                     libraryName = token;
1392                     start = firstSlash+1;
1393 
1394                     //Now that libraryName exists, check libraryVersion
1395                     firstSlash = resourceId.indexOf('/', start);
1396                     if (firstSlash >= 0)
1397                     {
1398                         token = resourceId.substring(start, firstSlash);
1399                         if (LIBRARY_VERSION_CHECKER.matcher(token).matches())
1400                         {
1401                             libraryVersion = token;
1402                             start = firstSlash+1;
1403                         }
1404                     }
1405                 }
1406 
1407                 firstSlash = resourceId.indexOf('/', start);
1408                 if (firstSlash < 0)
1409                 {
1410                     //no slashes.
1411                     resourceName = resourceId.substring(start);
1412                 }
1413                 else
1414                 {
1415                     // Check resource version. 
1416                     if (resourceVersion != null)
1417                     {
1418                         resourceName = resourceId.substring(start,lastSlash);
1419                     }
1420                     else
1421                     {
1422                         //no resource version, assume the remaining to be resource name
1423                         resourceName = resourceId.substring(start);
1424                     }
1425                 }
1426             }
1427         }
1428 
1429         //Check libraryName and resourceName
1430         if (resourceName == null)
1431         {
1432             return null;
1433         }
1434         if (!ResourceValidationUtils.isValidResourceName(resourceName))
1435         {
1436             return null;
1437         }
1438 
1439         if (libraryName != null && !ResourceValidationUtils.isValidLibraryName(
1440                 libraryName, isAllowSlashesLibraryName()))
1441         {
1442             return null;
1443         }
1444 
1445         // If some variable is "" set it as null.
1446         if (localePrefix != null && localePrefix.length() == 0)
1447         {
1448             localePrefix = null;
1449         }
1450         if (libraryName != null && libraryName.length() == 0)
1451         {
1452             libraryName = null;
1453         }
1454         if (libraryVersion != null && libraryVersion.length() == 0)
1455         {
1456             libraryVersion = null;
1457         }
1458         if (resourceName != null && resourceName.length() == 0)
1459         {
1460             resourceName = null;
1461         }
1462         if (resourceVersion != null && resourceVersion.length() == 0)
1463         {
1464             resourceVersion = null;
1465         }
1466 
1467         resourceMeta = resourceLoader.createResourceMeta(
1468             localePrefix, libraryName, libraryVersion, resourceName, resourceVersion, contractName);
1469 
1470         if (resourceMeta != null &&
1471             !resourceLoader.resourceExists(resourceMeta))
1472         {
1473             resourceMeta = null;
1474         }
1475         //}
1476         return resourceMeta;
1477     }
1478     
1479     protected ResourceMeta deriveViewResourceMeta(FacesContext context, ResourceLoader resourceLoader,
1480             String resourceName, String localePrefix)
1481     {
1482         ResourceMeta resourceMeta = null;
1483         String resourceVersion = null;
1484 
1485         //1. Try to locate resource in a localized path
1486         if (localePrefix != null)
1487         {
1488             resourceVersion = resourceLoader
1489                     .getResourceVersion(localePrefix + '/'+ resourceName);
1490             if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
1491             {
1492                 resourceMeta = resourceLoader.createResourceMeta(localePrefix, null, null,
1493                          resourceName, resourceVersion);
1494             }
1495 
1496             if (resourceMeta != null && !resourceLoader.resourceExists(resourceMeta))
1497             {
1498                 resourceMeta = null;
1499             }            
1500         }
1501         
1502         //2. Try to localize resource in a non localized path
1503         if (resourceMeta == null)
1504         {
1505             resourceVersion = resourceLoader
1506                     .getResourceVersion(resourceName);
1507             if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
1508             {
1509                 resourceMeta = resourceLoader.createResourceMeta(null, null, null,
1510                          resourceName, resourceVersion);
1511             }
1512 
1513             if (resourceMeta != null && !resourceLoader.resourceExists(resourceMeta))
1514             {
1515                 resourceMeta = null;
1516             }            
1517         }
1518 
1519         return resourceMeta;        
1520     }
1521     
1522     protected ResourceMeta deriveViewResourceMeta(FacesContext context, ContractResourceLoader resourceLoader,
1523             String resourceName, String localePrefix, String contractName)
1524     {
1525         ResourceMeta resourceMeta = null;
1526         String resourceVersion = null;
1527 
1528         //1. Try to locate resource in a localized path
1529         if (localePrefix != null)
1530         {
1531             resourceVersion = resourceLoader
1532                     .getResourceVersion(localePrefix + '/'+ resourceName, contractName);
1533             if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
1534             {
1535                 resourceMeta = resourceLoader.createResourceMeta(localePrefix, null, null,
1536                      resourceName, resourceVersion, contractName);
1537             }
1538 
1539             if (resourceMeta != null && !resourceLoader.resourceExists(resourceMeta))
1540             {
1541                 resourceMeta = null;
1542             }            
1543         }
1544         
1545         //2. Try to localize resource in a non localized path
1546         if (resourceMeta == null)
1547         {
1548             resourceVersion = resourceLoader
1549                     .getResourceVersion(resourceName, contractName);
1550             if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
1551             {
1552                 resourceMeta = resourceLoader.createResourceMeta(null, null, null,
1553                          resourceName, resourceVersion, contractName);
1554             }
1555 
1556             if (resourceMeta != null && !resourceLoader.resourceExists(resourceMeta))
1557             {
1558                 resourceMeta = null;
1559             }            
1560         }
1561 
1562         return resourceMeta;
1563     }
1564 
1565     @Override
1566     public Resource createViewResource(FacesContext facesContext, String resourceName)
1567     {
1568         // There are some special points to remember for a view resource in comparison
1569         // with a normal resource:
1570         //
1571         // - A view resource never has an associated library name 
1572         //   (this was done to keep simplicity).
1573         // - A view resource can be inside a resource library contract.
1574         // - A view resource could be internationalized in the same way a normal resource.
1575         // - A view resource can be created from the webapp root folder, 
1576         //   a normal resource cannot.
1577         // - A view resource cannot be created from /resources or META-INF/resources.
1578         // 
1579         // For example, a valid resourceId for a view resource is like this:
1580         //
1581         // [localePrefix/]resourceName[/resourceVersion]
1582         //
1583         // but the resource loader can ignore localePrefix or resourceVersion, like
1584         // for example the webapp root folder.
1585         // 
1586         // When createViewResource() is called, the view must be used to derive
1587         // the localePrefix and facesContext must be used to get the available contracts.
1588         
1589         Resource resource = null;
1590 
1591         if (resourceName == null)
1592         {
1593             throw new NullPointerException();
1594         }
1595         if (resourceName.charAt(0) == '/')
1596         {
1597             // If resourceName starts with '/', remove that character because it
1598             // does not have any meaning (with and without should point to the 
1599             // same resource).
1600             resourceName = resourceName.substring(1);
1601         }
1602         
1603         // Later in deriveResourceMeta the resourceId is decomposed and
1604         // its elements validated properly.
1605         if (!ResourceValidationUtils.isValidViewResource(resourceName))
1606         {
1607             return null;
1608         }
1609         final String localePrefix = getLocalePrefixForLocateResource(facesContext);
1610         String contentType = facesContext.getExternalContext().getMimeType(resourceName);
1611         final List<String> contracts = facesContext.getResourceLibraryContracts(); 
1612         String contractPreferred = getContractNameForLocateResource(facesContext);
1613         ResourceValue resourceValue = null;
1614         
1615         // Check cache:
1616         //
1617         // Contracts are on top of everything, because it is a concept that defines
1618         // resources in a application scope concept. It means all resources in
1619         // /resources or /META-INF/resources can be overriden using a contract. Note
1620         // it also means resources under /META-INF/flows can also be overriden using
1621         // a contract.
1622         if (contractPreferred != null)
1623         {
1624             resourceValue = getResourceLoaderCache().getViewResource(
1625                     resourceName, contentType, localePrefix, contractPreferred);
1626         }
1627         if (resourceValue == null && !contracts.isEmpty())
1628         {
1629             // Try to get resource but try with a contract name
1630             for (String contract : contracts)
1631             {
1632                 resourceValue = getResourceLoaderCache().getViewResource(
1633                     resourceName, contentType, localePrefix, contract);
1634                 if (resourceValue != null)
1635                 {
1636                     break;
1637                 }
1638             }
1639         }
1640         if (resourceValue == null)
1641         {
1642             // Try to get resource without contract name
1643             resourceValue = getResourceLoaderCache().getViewResource(
1644                 resourceName, contentType, localePrefix);
1645         }
1646 
1647         if(resourceValue != null)
1648         {        
1649             resource = new ResourceImpl(resourceValue.getResourceMeta(), resourceValue.getResourceLoader(),
1650                     getResourceHandlerSupport(), contentType, 
1651                     resourceValue.getCachedInfo() != null ? resourceValue.getCachedInfo().getURL() : null, null);
1652         }
1653         else
1654         {
1655             boolean resolved = false;
1656             if (contractPreferred != null)
1657             {
1658                 for (ContractResourceLoader loader : getResourceHandlerSupport().getContractResourceLoaders())
1659                 {
1660                     ResourceMeta resourceMeta = deriveViewResourceMeta(
1661                         facesContext, loader, resourceName, localePrefix, contractPreferred);
1662                     if (resourceMeta != null)
1663                     {
1664                         resource = new ResourceImpl(resourceMeta, loader, 
1665                             getResourceHandlerSupport(), contentType);
1666 
1667                         // cache it
1668                         getResourceLoaderCache().putViewResource(
1669                             resourceName, contentType, localePrefix, contractPreferred, resourceMeta, loader, 
1670                             new ResourceCachedInfo(resource.getURL(), null));
1671                         
1672                         resolved = true;
1673                         break;
1674                     }
1675                 }
1676             }
1677             if (!resolved && !contracts.isEmpty())
1678             {
1679                 for (ContractResourceLoader loader : 
1680                         getResourceHandlerSupport().getContractResourceLoaders())
1681                 {
1682                     for (String contract : contracts)
1683                     {
1684                         ResourceMeta resourceMeta = deriveViewResourceMeta(
1685                             facesContext, loader, resourceName, localePrefix, contract);
1686                         if (resourceMeta != null)
1687                         {
1688                             resource = new ResourceImpl(resourceMeta, loader, 
1689                                 getResourceHandlerSupport(), contentType);
1690 
1691                             // cache it
1692                             getResourceLoaderCache().putViewResource(
1693                                 resourceName, contentType, localePrefix, contract, resourceMeta, loader,
1694                                 new ResourceCachedInfo(resource.getURL(), null));
1695 
1696                             resolved = true;
1697                             break;
1698                         }
1699                     }
1700                 }
1701             }
1702             if (!resolved)
1703             {
1704                 // "... Considering the web app root ..."
1705                 
1706                 // "... Considering faces flows (at the locations specified in the spec prose document section 
1707                 // Faces Flows in the Using JSF in Web Applications chapter) ..."
1708                 for (ResourceLoader loader : getResourceHandlerSupport().getViewResourceLoaders())
1709                 {
1710                     ResourceMeta resourceMeta = deriveViewResourceMeta(
1711                         facesContext, loader, resourceName, localePrefix);
1712 
1713                     if (resourceMeta != null)
1714                     {
1715                         resource = new ResourceImpl(resourceMeta, loader, getResourceHandlerSupport(), contentType);
1716 
1717                         // cache it
1718                         getResourceLoaderCache().putViewResource(
1719                             resourceName, contentType, localePrefix, resourceMeta, loader,
1720                             new ResourceCachedInfo(resource.getURL(), null));
1721                         break;
1722                     }
1723                 }
1724             }
1725         }
1726         return resource;
1727     }
1728 
1729 }