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: 1542444 $ $Date: 2013-11-15 20:41:08 -0500 (Fri, 15 Nov 2013) $
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         
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             if (!contracts.isEmpty())
814             {
815                 for (String contract : contracts)
816                 {
817                     for (ContractResourceLoader loader : getResourceHandlerSupport()
818                             .getContractResourceLoaders())
819                     {
820                         if (loader.libraryExists(pathToLib, contract))
821                         {
822                             return true;
823                         }
824                     }
825                 }
826             }
827             
828             for (ResourceLoader loader : getResourceHandlerSupport()
829                     .getResourceLoaders())
830             {
831                 if (loader.libraryExists(pathToLib))
832                 {
833                     return true;
834                 }
835             }            
836         }
837 
838         //Check without locale
839         if (!contracts.isEmpty())
840         {
841             for (String contract : contracts)
842             {
843                 for (ContractResourceLoader loader : getResourceHandlerSupport()
844                         .getContractResourceLoaders())
845                 {
846                     if (loader.libraryExists(contract, libraryName))
847                     {
848                         return true;
849                     }
850                 }
851             }
852         }
853 
854         for (ResourceLoader loader : getResourceHandlerSupport()
855                 .getResourceLoaders())
856         {
857             if (loader.libraryExists(libraryName))
858             {
859                 return true;
860             }
861         }
862 
863         return false;
864     }
865 
866     /**
867      * @param resourceHandlerSupport
868      *            the resourceHandlerSupport to set
869      */
870     public void setResourceHandlerSupport(
871             ResourceHandlerSupport resourceHandlerSupport)
872     {
873         _resourceHandlerSupport = resourceHandlerSupport;
874     }
875 
876     /**
877      * @return the resourceHandlerSupport
878      */
879     protected ResourceHandlerSupport getResourceHandlerSupport()
880     {
881         if (_resourceHandlerSupport == null)
882         {
883             _resourceHandlerSupport = new DefaultResourceHandlerSupport();
884         }
885         return _resourceHandlerSupport;
886     }
887 
888     private ResourceHandlerCache getResourceLoaderCache()
889     {
890         if (_resourceHandlerCache == null)
891         {
892             _resourceHandlerCache = new ResourceHandlerCache();
893         }
894         return _resourceHandlerCache;
895     }
896 
897     private String _getContentType(Resource resource, ExternalContext externalContext)
898     {
899         String contentType = resource.getContentType();
900 
901         // the resource does not provide a content-type --> determine it via mime-type
902         if (contentType == null || contentType.length() == 0)
903         {
904             String resourceName = getWrappedResourceName(resource);
905 
906             if (resourceName != null)
907             {
908                 contentType = externalContext.getMimeType(resourceName);
909             }
910         }
911 
912         return contentType;
913     }
914 
915     /**
916      * Recursively unwarp the resource until we find the real resourceName
917      * This is needed because the JSF2 specced ResourceWrapper doesn't override
918      * the getResourceName() method :(
919      * @param resource
920      * @return the first non-null resourceName or <code>null</code> if none set
921      */
922     private String getWrappedResourceName(Resource resource)
923     {
924         String resourceName = resource.getResourceName();
925         if (resourceName != null)
926         {
927             return resourceName;
928         }
929 
930         if (resource instanceof ResourceWrapper)
931         {
932             return getWrappedResourceName(((ResourceWrapper) resource).getWrapped());
933         }
934 
935         return null;
936     }
937     
938     protected boolean isAllowSlashesLibraryName()
939     {
940         if (_allowSlashLibraryName == null)
941         {
942             _allowSlashLibraryName = WebConfigParamUtils.getBooleanInitParameter(
943                     FacesContext.getCurrentInstance().getExternalContext(), 
944                     INIT_PARAM_STRICT_JSF_2_ALLOW_SLASH_LIBRARY_NAME,
945                     INIT_PARAM_STRICT_JSF_2_ALLOW_SLASH_LIBRARY_NAME_DEFAULT);
946         }
947         return _allowSlashLibraryName;
948     }
949 
950     protected int getResourceBufferSize()
951     {
952         if (_resourceBufferSize == -1)
953         {
954             _resourceBufferSize = WebConfigParamUtils.getIntegerInitParameter(
955                 FacesContext.getCurrentInstance().getExternalContext(),
956                 INIT_PARAM_RESOURCE_BUFFER_SIZE,
957                 INIT_PARAM_RESOURCE_BUFFER_SIZE_DEFAULT);
958         }
959         return _resourceBufferSize;
960     }
961 
962     @Override
963     public Resource createResourceFromId(String resourceId)
964     {
965         Resource resource = null;
966 
967         if (resourceId == null)
968         {
969             throw new NullPointerException();
970         }
971         
972         // Later in deriveResourceMeta the resourceId is decomposed and
973         // its elements validated properly.
974         if (!ResourceValidationUtils.isValidResourceId(resourceId))
975         {
976             return null;
977         }
978         
979         FacesContext facesContext = FacesContext.getCurrentInstance();
980         final List<String> contracts = facesContext.getResourceLibraryContracts(); 
981         String contractPreferred = getContractNameForLocateResource(facesContext);
982         ResourceValue resourceValue = null;
983         
984         // Check cache:
985         //
986         // Contracts are on top of everything, because it is a concept that defines
987         // resources in a application scope concept. It means all resources in
988         // /resources or /META-INF/resources can be overriden using a contract. Note
989         // it also means resources under /META-INF/flows can also be overriden using
990         // a contract.
991         if (contractPreferred != null)
992         {
993             resourceValue = getResourceLoaderCache().getResource(
994                     resourceId, contractPreferred);
995         }
996         if (resourceValue == null && !contracts.isEmpty())
997         {
998             // Try to get resource but try with a contract name
999             for (String contract : contracts)
1000             {
1001                 resourceValue = getResourceLoaderCache().getResource(resourceId, contract);
1002                 if (resourceValue != null)
1003                 {
1004                     break;
1005                 }
1006             }
1007         }
1008         if (resourceValue == null)
1009         {
1010             // Try to get resource without contract name
1011             resourceValue = getResourceLoaderCache().getResource(resourceId);
1012         }
1013         
1014         if(resourceValue != null)
1015         {        
1016             //Resolve contentType using ExternalContext.getMimeType
1017             String contentType = facesContext.getExternalContext().getMimeType(
1018                 resourceValue.getResourceMeta().getResourceName());
1019 
1020             resource = new ResourceImpl(resourceValue.getResourceMeta(), resourceValue.getResourceLoader(),
1021                     getResourceHandlerSupport(), contentType,
1022                     resourceValue.getCachedInfo() != null ? resourceValue.getCachedInfo().getURL() : null, 
1023                     resourceValue.getCachedInfo() != null ? resourceValue.getCachedInfo().getRequestPath() : null);
1024         }
1025         else
1026         {
1027             boolean resolved = false;
1028             if (contractPreferred != null)
1029             {
1030                 for (ContractResourceLoader loader : getResourceHandlerSupport().getContractResourceLoaders())
1031                 {
1032                     ResourceMeta resourceMeta = deriveResourceMeta(
1033                         facesContext, loader, resourceId, contractPreferred);
1034                     if (resourceMeta != null)
1035                     {
1036                         String contentType = facesContext.getExternalContext().getMimeType(
1037                             resourceMeta.getResourceName());
1038                         
1039                         resource = new ResourceImpl(resourceMeta, loader, 
1040                             getResourceHandlerSupport(), contentType);
1041 
1042                         // cache it
1043                         getResourceLoaderCache().putResource(resourceId, resourceMeta, loader, 
1044                             new ResourceCachedInfo(resource.getURL(), resource.getRequestPath()));
1045                         
1046                         resolved = true;
1047                         break;
1048                     }
1049                 }
1050             }
1051             if (!resolved && !contracts.isEmpty())
1052             {
1053                 for (ContractResourceLoader loader : 
1054                         getResourceHandlerSupport().getContractResourceLoaders())
1055                 {
1056                     for (String contract : contracts)
1057                     {
1058                         ResourceMeta resourceMeta = deriveResourceMeta(
1059                             facesContext, loader, resourceId, contract);
1060                         if (resourceMeta != null)
1061                         {
1062                             String contentType = facesContext.getExternalContext().getMimeType(
1063                                 resourceMeta.getResourceName());
1064 
1065                             resource = new ResourceImpl(resourceMeta, loader, 
1066                                 getResourceHandlerSupport(), contentType);
1067 
1068                             // cache it
1069                             getResourceLoaderCache().putResource(resourceId, resourceMeta, loader, 
1070                                 new ResourceCachedInfo(resource.getURL(), resource.getRequestPath()));
1071 
1072                             resolved = true;
1073                             break;
1074                         }
1075                     }
1076                 }
1077             }
1078             if (!resolved)
1079             {
1080                 for (ResourceLoader loader : getResourceHandlerSupport().getResourceLoaders())
1081                 {
1082                     ResourceMeta resourceMeta = deriveResourceMeta(facesContext, loader, resourceId);
1083 
1084                     if (resourceMeta != null)
1085                     {
1086                         String contentType = facesContext.getExternalContext().getMimeType(
1087                             resourceMeta.getResourceName());
1088 
1089                         resource = new ResourceImpl(resourceMeta, loader, getResourceHandlerSupport(), contentType);
1090 
1091                         // cache it
1092                         getResourceLoaderCache().putResource(resourceId, resourceMeta, loader, 
1093                             new ResourceCachedInfo(resource.getURL(), resource.getRequestPath()));
1094                         break;
1095                     }
1096                 }
1097             }
1098         }
1099         return resource;
1100     }
1101 
1102     protected ResourceMeta deriveResourceMeta(FacesContext context, ResourceLoader resourceLoader,
1103             String resourceId)
1104     {
1105         ResourceMeta resourceMeta = null;
1106         String token = null;
1107         String localePrefix = null;
1108         String libraryName = null;
1109         String libraryVersion = null;
1110         String resourceName = null;
1111         String resourceVersion = null;
1112         
1113         // Check if resource exists. It avoids additional 
1114         // checks and it can be done very quickly because the 
1115         // loader always uses the resourceId structure to
1116         // organize resources. But decompose the resourceId is
1117         // even faster.
1118         //if (resourceLoader.resourceIdExists(resourceId))
1119         //{
1120         int lastSlash = resourceId.lastIndexOf('/');
1121         if (lastSlash < 0)
1122         {
1123             //no slashes, so it is just a plain resource.
1124             resourceName = resourceId;
1125         }
1126         else
1127         {
1128             token = resourceId.substring(lastSlash+1);
1129             if (RESOURCE_VERSION_CHECKER.matcher(token).matches())
1130             {
1131                 int secondLastSlash = resourceId.lastIndexOf('/', lastSlash-1);
1132                 if (secondLastSlash < 0)
1133                 {
1134                     secondLastSlash = 0;
1135                 }
1136 
1137                 String rnToken = resourceId.substring(secondLastSlash+1, lastSlash);
1138                 int lastPoint = rnToken.lastIndexOf('.');
1139                 // lastPoint < 0 means it does not match, the token is not a resource version
1140                 if (lastPoint >= 0)
1141                 {
1142                     String ext = rnToken.substring(lastPoint);
1143                     if (token.endsWith(ext))
1144                     {
1145                         //It match a versioned resource
1146                         resourceVersion = token.substring(0,token.length()-ext.length());
1147                     }
1148                 }
1149             }
1150 
1151             // 1. Extract the library path and locale prefix if necessary
1152             int start = 0;
1153             int firstSlash = resourceId.indexOf('/');
1154 
1155             // At least one slash, check if the start is locale prefix.
1156             String bundleName = context.getApplication().getMessageBundle();
1157             //If no bundle set, it can't be localePrefix
1158             if (null != bundleName)
1159             {
1160                 token = resourceId.substring(start, firstSlash);
1161                 //Try to derive a locale object
1162                 Locale locale = _LocaleUtils.deriveLocale(token);
1163 
1164                 // If the locale was derived and it is available, 
1165                 // assume that portion of the resourceId it as a locale prefix.
1166                 if (locale != null && _LocaleUtils.isAvailableLocale(locale))
1167                 {
1168                     localePrefix = token;
1169                     start = firstSlash+1;
1170                 }
1171             }
1172 
1173             //Check slash again from start
1174             firstSlash = resourceId.indexOf('/', start);
1175             if (firstSlash < 0)
1176             {
1177                 //no slashes.
1178                 resourceName = resourceId.substring(start);
1179             }
1180             else
1181             {
1182                 //check libraryName
1183                 token = resourceId.substring(start, firstSlash);
1184                 int minResourceNameSlash = (resourceVersion != null) ?
1185                     resourceId.lastIndexOf('/', lastSlash-1) : lastSlash;
1186                 //if (resourceLoader.libraryExists(token))
1187                 if (start < minResourceNameSlash)
1188                 {
1189                     libraryName = token;
1190                     start = firstSlash+1;
1191 
1192                     //Now that libraryName exists, check libraryVersion
1193                     firstSlash = resourceId.indexOf('/', start);
1194                     if (firstSlash >= 0)
1195                     {
1196                         token = resourceId.substring(start, firstSlash);
1197                         if (LIBRARY_VERSION_CHECKER.matcher(token).matches())
1198                         {
1199                             libraryVersion = token;
1200                             start = firstSlash+1;
1201                         }
1202                     }
1203                 }
1204 
1205                 firstSlash = resourceId.indexOf('/', start);
1206                 if (firstSlash < 0)
1207                 {
1208                     //no slashes.
1209                     resourceName = resourceId.substring(start);
1210                 }
1211                 else
1212                 {
1213                     // Check resource version. 
1214                     if (resourceVersion != null)
1215                     {
1216                         resourceName = resourceId.substring(start,lastSlash);
1217                     }
1218                     else
1219                     {
1220                         //no resource version, assume the remaining to be resource name
1221                         resourceName = resourceId.substring(start);
1222                     }
1223                 }
1224             }
1225         }
1226 
1227         //Check libraryName and resourceName
1228         if (resourceName == null)
1229         {
1230             return null;
1231         }
1232         if (!ResourceValidationUtils.isValidResourceName(resourceName))
1233         {
1234             return null;
1235         }
1236 
1237         if (libraryName != null && !ResourceValidationUtils.isValidLibraryName(
1238                 libraryName, isAllowSlashesLibraryName()))
1239         {
1240             return null;
1241         }
1242 
1243         // If some variable is "" set it as null.
1244         if (localePrefix != null && localePrefix.length() == 0)
1245         {
1246             localePrefix = null;
1247         }
1248         if (libraryName != null && libraryName.length() == 0)
1249         {
1250             libraryName = null;
1251         }
1252         if (libraryVersion != null && libraryVersion.length() == 0)
1253         {
1254             libraryVersion = null;
1255         }
1256         if (resourceName != null && resourceName.length() == 0)
1257         {
1258             resourceName = null;
1259         }
1260         if (resourceVersion != null && resourceVersion.length() == 0)
1261         {
1262             resourceVersion = null;
1263         }
1264 
1265         resourceMeta = resourceLoader.createResourceMeta(
1266             localePrefix, libraryName, libraryVersion, resourceName, resourceVersion);
1267 
1268         if (resourceMeta != null &&
1269             !resourceLoader.resourceExists(resourceMeta))
1270         {
1271             resourceMeta = null;
1272         }
1273         //}
1274         return resourceMeta;
1275     }
1276     
1277     protected ResourceMeta deriveResourceMeta(FacesContext context, ContractResourceLoader resourceLoader,
1278             String resourceId, String contractName)
1279     {
1280         ResourceMeta resourceMeta = null;
1281         String token = null;
1282         String localePrefix = null;
1283         String libraryName = null;
1284         String libraryVersion = null;
1285         String resourceName = null;
1286         String resourceVersion = null;
1287         
1288         // Check if resource exists. It avoids additional 
1289         // checks and it can be done very quickly because the 
1290         // loader always uses the resourceId structure to
1291         // organize resources. But decompose the resourceId is
1292         // even faster.
1293         //if (resourceLoader.resourceIdExists(resourceId))
1294         //{
1295         int lastSlash = resourceId.lastIndexOf('/');
1296         if (lastSlash < 0)
1297         {
1298             //no slashes, so it is just a plain resource.
1299             resourceName = resourceId;
1300         }
1301         else
1302         {
1303             token = resourceId.substring(lastSlash+1);
1304             if (RESOURCE_VERSION_CHECKER.matcher(token).matches())
1305             {
1306                 int secondLastSlash = resourceId.lastIndexOf('/', lastSlash-1);
1307                 if (secondLastSlash < 0)
1308                 {
1309                     secondLastSlash = 0;
1310                 }
1311 
1312                 String rnToken = resourceId.substring(secondLastSlash+1, lastSlash);
1313                 int lastPoint = rnToken.lastIndexOf('.');
1314                 // lastPoint < 0 means it does not match, the token is not a resource version
1315                 if (lastPoint >= 0)
1316                 {
1317                     String ext = rnToken.substring(lastPoint);
1318                     if (token.endsWith(ext))
1319                     {
1320                         //It match a versioned resource
1321                         resourceVersion = token.substring(0,token.length()-ext.length());
1322                     }
1323                 }
1324             }
1325 
1326             // 1. Extract the library path and locale prefix if necessary
1327             int start = 0;
1328             int firstSlash = resourceId.indexOf('/');
1329 
1330             // At least one slash, check if the start is locale prefix.
1331             String bundleName = context.getApplication().getMessageBundle();
1332             //If no bundle set, it can't be localePrefix
1333             if (null != bundleName)
1334             {
1335                 token = resourceId.substring(start, firstSlash);
1336                 //Try to derive a locale object
1337                 Locale locale = _LocaleUtils.deriveLocale(token);
1338 
1339                 // If the locale was derived and it is available, 
1340                 // assume that portion of the resourceId it as a locale prefix.
1341                 if (locale != null && _LocaleUtils.isAvailableLocale(locale))
1342                 {
1343                     localePrefix = token;
1344                     start = firstSlash+1;
1345                 }
1346             }
1347 
1348             //Check slash again from start
1349             firstSlash = resourceId.indexOf('/', start);
1350             if (firstSlash < 0)
1351             {
1352                 //no slashes.
1353                 resourceName = resourceId.substring(start);
1354             }
1355             else
1356             {
1357                 //check libraryName
1358                 token = resourceId.substring(start, firstSlash);
1359                 int minResourceNameSlash = (resourceVersion != null) ?
1360                     resourceId.lastIndexOf('/', lastSlash-1) : lastSlash;
1361                 //if (resourceLoader.libraryExists(token))
1362                 if (start < minResourceNameSlash)
1363                 {
1364                     libraryName = token;
1365                     start = firstSlash+1;
1366 
1367                     //Now that libraryName exists, check libraryVersion
1368                     firstSlash = resourceId.indexOf('/', start);
1369                     if (firstSlash >= 0)
1370                     {
1371                         token = resourceId.substring(start, firstSlash);
1372                         if (LIBRARY_VERSION_CHECKER.matcher(token).matches())
1373                         {
1374                             libraryVersion = token;
1375                             start = firstSlash+1;
1376                         }
1377                     }
1378                 }
1379 
1380                 firstSlash = resourceId.indexOf('/', start);
1381                 if (firstSlash < 0)
1382                 {
1383                     //no slashes.
1384                     resourceName = resourceId.substring(start);
1385                 }
1386                 else
1387                 {
1388                     // Check resource version. 
1389                     if (resourceVersion != null)
1390                     {
1391                         resourceName = resourceId.substring(start,lastSlash);
1392                     }
1393                     else
1394                     {
1395                         //no resource version, assume the remaining to be resource name
1396                         resourceName = resourceId.substring(start);
1397                     }
1398                 }
1399             }
1400         }
1401 
1402         //Check libraryName and resourceName
1403         if (resourceName == null)
1404         {
1405             return null;
1406         }
1407         if (!ResourceValidationUtils.isValidResourceName(resourceName))
1408         {
1409             return null;
1410         }
1411 
1412         if (libraryName != null && !ResourceValidationUtils.isValidLibraryName(
1413                 libraryName, isAllowSlashesLibraryName()))
1414         {
1415             return null;
1416         }
1417 
1418         // If some variable is "" set it as null.
1419         if (localePrefix != null && localePrefix.length() == 0)
1420         {
1421             localePrefix = null;
1422         }
1423         if (libraryName != null && libraryName.length() == 0)
1424         {
1425             libraryName = null;
1426         }
1427         if (libraryVersion != null && libraryVersion.length() == 0)
1428         {
1429             libraryVersion = null;
1430         }
1431         if (resourceName != null && resourceName.length() == 0)
1432         {
1433             resourceName = null;
1434         }
1435         if (resourceVersion != null && resourceVersion.length() == 0)
1436         {
1437             resourceVersion = null;
1438         }
1439 
1440         resourceMeta = resourceLoader.createResourceMeta(
1441             localePrefix, libraryName, libraryVersion, resourceName, resourceVersion, contractName);
1442 
1443         if (resourceMeta != null &&
1444             !resourceLoader.resourceExists(resourceMeta))
1445         {
1446             resourceMeta = null;
1447         }
1448         //}
1449         return resourceMeta;
1450     }
1451     
1452     protected ResourceMeta deriveViewResourceMeta(FacesContext context, ResourceLoader resourceLoader,
1453             String resourceName, String localePrefix)
1454     {
1455         ResourceMeta resourceMeta = null;
1456         String resourceVersion = null;
1457 
1458         //1. Try to locate resource in a localized path
1459         if (localePrefix != null)
1460         {
1461             resourceVersion = resourceLoader
1462                     .getResourceVersion(localePrefix + '/'+ resourceName);
1463             if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
1464             {
1465                 resourceMeta = resourceLoader.createResourceMeta(localePrefix, null, null,
1466                          resourceName, resourceVersion);
1467             }
1468 
1469             if (resourceMeta != null && !resourceLoader.resourceExists(resourceMeta))
1470             {
1471                 resourceMeta = null;
1472             }            
1473         }
1474         
1475         //2. Try to localize resource in a non localized path
1476         if (resourceMeta == null)
1477         {
1478             resourceVersion = resourceLoader
1479                     .getResourceVersion(resourceName);
1480             if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
1481             {
1482                 resourceMeta = resourceLoader.createResourceMeta(null, null, null,
1483                          resourceName, resourceVersion);
1484             }
1485 
1486             if (resourceMeta != null && !resourceLoader.resourceExists(resourceMeta))
1487             {
1488                 resourceMeta = null;
1489             }            
1490         }
1491 
1492         return resourceMeta;        
1493     }
1494     
1495     protected ResourceMeta deriveViewResourceMeta(FacesContext context, ContractResourceLoader resourceLoader,
1496             String resourceName, String localePrefix, String contractName)
1497     {
1498         ResourceMeta resourceMeta = null;
1499         String resourceVersion = null;
1500 
1501         //1. Try to locate resource in a localized path
1502         if (localePrefix != null)
1503         {
1504             resourceVersion = resourceLoader
1505                     .getResourceVersion(localePrefix + '/'+ resourceName, contractName);
1506             if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
1507             {
1508                 resourceMeta = resourceLoader.createResourceMeta(localePrefix, null, null,
1509                      resourceName, resourceVersion, contractName);
1510             }
1511 
1512             if (resourceMeta != null && !resourceLoader.resourceExists(resourceMeta))
1513             {
1514                 resourceMeta = null;
1515             }            
1516         }
1517         
1518         //2. Try to localize resource in a non localized path
1519         if (resourceMeta == null)
1520         {
1521             resourceVersion = resourceLoader
1522                     .getResourceVersion(resourceName, contractName);
1523             if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
1524             {
1525                 resourceMeta = resourceLoader.createResourceMeta(null, null, null,
1526                          resourceName, resourceVersion, contractName);
1527             }
1528 
1529             if (resourceMeta != null && !resourceLoader.resourceExists(resourceMeta))
1530             {
1531                 resourceMeta = null;
1532             }            
1533         }
1534 
1535         return resourceMeta;
1536     }
1537 
1538     @Override
1539     public Resource createViewResource(FacesContext facesContext, String resourceName)
1540     {
1541         // There are some special points to remember for a view resource in comparison
1542         // with a normal resource:
1543         //
1544         // - A view resource never has an associated library name 
1545         //   (this was done to keep simplicity).
1546         // - A view resource can be inside a resource library contract.
1547         // - A view resource could be internationalized in the same way a normal resource.
1548         // - A view resource can be created from the webapp root folder, 
1549         //   a normal resource cannot.
1550         // - A view resource cannot be created from /resources or META-INF/resources.
1551         // 
1552         // For example, a valid resourceId for a view resource is like this:
1553         //
1554         // [localePrefix/]resourceName[/resourceVersion]
1555         //
1556         // but the resource loader can ignore localePrefix or resourceVersion, like
1557         // for example the webapp root folder.
1558         // 
1559         // When createViewResource() is called, the view must be used to derive
1560         // the localePrefix and facesContext must be used to get the available contracts.
1561         
1562         Resource resource = null;
1563 
1564         if (resourceName == null)
1565         {
1566             throw new NullPointerException();
1567         }
1568         if (resourceName.charAt(0) == '/')
1569         {
1570             // If resourceName starts with '/', remove that character because it
1571             // does not have any meaning (with and without should point to the 
1572             // same resource).
1573             resourceName = resourceName.substring(1);
1574         }
1575         
1576         // Later in deriveResourceMeta the resourceId is decomposed and
1577         // its elements validated properly.
1578         if (!ResourceValidationUtils.isValidViewResource(resourceName))
1579         {
1580             return null;
1581         }
1582         final String localePrefix = getLocalePrefixForLocateResource(facesContext);
1583         String contentType = facesContext.getExternalContext().getMimeType(resourceName);
1584         final List<String> contracts = facesContext.getResourceLibraryContracts(); 
1585         String contractPreferred = getContractNameForLocateResource(facesContext);
1586         ResourceValue resourceValue = null;
1587         
1588         // Check cache:
1589         //
1590         // Contracts are on top of everything, because it is a concept that defines
1591         // resources in a application scope concept. It means all resources in
1592         // /resources or /META-INF/resources can be overriden using a contract. Note
1593         // it also means resources under /META-INF/flows can also be overriden using
1594         // a contract.
1595         if (contractPreferred != null)
1596         {
1597             resourceValue = getResourceLoaderCache().getViewResource(
1598                     resourceName, contentType, localePrefix, contractPreferred);
1599         }
1600         if (resourceValue == null && !contracts.isEmpty())
1601         {
1602             // Try to get resource but try with a contract name
1603             for (String contract : contracts)
1604             {
1605                 resourceValue = getResourceLoaderCache().getViewResource(
1606                     resourceName, contentType, localePrefix, contract);
1607                 if (resourceValue != null)
1608                 {
1609                     break;
1610                 }
1611             }
1612         }
1613         if (resourceValue == null)
1614         {
1615             // Try to get resource without contract name
1616             resourceValue = getResourceLoaderCache().getViewResource(
1617                 resourceName, contentType, localePrefix);
1618         }
1619 
1620         if(resourceValue != null)
1621         {        
1622             resource = new ResourceImpl(resourceValue.getResourceMeta(), resourceValue.getResourceLoader(),
1623                     getResourceHandlerSupport(), contentType, 
1624                     resourceValue.getCachedInfo() != null ? resourceValue.getCachedInfo().getURL() : null, null);
1625         }
1626         else
1627         {
1628             boolean resolved = false;
1629             if (contractPreferred != null)
1630             {
1631                 for (ContractResourceLoader loader : getResourceHandlerSupport().getContractResourceLoaders())
1632                 {
1633                     ResourceMeta resourceMeta = deriveViewResourceMeta(
1634                         facesContext, loader, resourceName, localePrefix, contractPreferred);
1635                     if (resourceMeta != null)
1636                     {
1637                         resource = new ResourceImpl(resourceMeta, loader, 
1638                             getResourceHandlerSupport(), contentType);
1639 
1640                         // cache it
1641                         getResourceLoaderCache().putViewResource(
1642                             resourceName, contentType, localePrefix, contractPreferred, resourceMeta, loader, 
1643                             new ResourceCachedInfo(resource.getURL(), null));
1644                         
1645                         resolved = true;
1646                         break;
1647                     }
1648                 }
1649             }
1650             if (!resolved && !contracts.isEmpty())
1651             {
1652                 for (ContractResourceLoader loader : 
1653                         getResourceHandlerSupport().getContractResourceLoaders())
1654                 {
1655                     for (String contract : contracts)
1656                     {
1657                         ResourceMeta resourceMeta = deriveViewResourceMeta(
1658                             facesContext, loader, resourceName, localePrefix, contract);
1659                         if (resourceMeta != null)
1660                         {
1661                             resource = new ResourceImpl(resourceMeta, loader, 
1662                                 getResourceHandlerSupport(), contentType);
1663 
1664                             // cache it
1665                             getResourceLoaderCache().putViewResource(
1666                                 resourceName, contentType, localePrefix, contract, resourceMeta, loader,
1667                                 new ResourceCachedInfo(resource.getURL(), null));
1668 
1669                             resolved = true;
1670                             break;
1671                         }
1672                     }
1673                 }
1674             }
1675             if (!resolved)
1676             {
1677                 // "... Considering the web app root ..."
1678                 
1679                 // "... Considering faces flows (at the locations specified in the spec prose document section 
1680                 // Faces Flows in the Using JSF in Web Applications chapter) ..."
1681                 for (ResourceLoader loader : getResourceHandlerSupport().getViewResourceLoaders())
1682                 {
1683                     ResourceMeta resourceMeta = deriveViewResourceMeta(
1684                         facesContext, loader, resourceName, localePrefix);
1685 
1686                     if (resourceMeta != null)
1687                     {
1688                         resource = new ResourceImpl(resourceMeta, loader, getResourceHandlerSupport(), contentType);
1689 
1690                         // cache it
1691                         getResourceLoaderCache().putViewResource(
1692                             resourceName, contentType, localePrefix, resourceMeta, loader,
1693                             new ResourceCachedInfo(resource.getURL(), null));
1694                         break;
1695                     }
1696                 }
1697             }
1698         }
1699         return resource;
1700     }
1701 
1702 }