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