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