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.commons.resourcehandler;
20  
21  import java.util.Map;
22  
23  import javax.faces.application.ProjectStage;
24  import javax.faces.application.Resource;
25  import javax.faces.application.ResourceHandler;
26  import javax.faces.context.ExternalContext;
27  import javax.faces.context.FacesContext;
28  import javax.faces.webapp.FacesServlet;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
33  import org.apache.myfaces.commons.resourcehandler.application.FacesServletMapping;
34  import org.apache.myfaces.commons.resourcehandler.config.MyFacesResourceHandlerConfigParser;
35  import org.apache.myfaces.commons.resourcehandler.config.element.MyFacesResourcesConfig;
36  import org.apache.myfaces.commons.resourcehandler.resource.BaseResourceHandlerSupport;
37  import org.apache.myfaces.commons.resourcehandler.resource.ClassLoaderResourceLoader;
38  import org.apache.myfaces.commons.resourcehandler.resource.ExternalContextResourceLoader;
39  import org.apache.myfaces.commons.resourcehandler.resource.ResourceLoader;
40  import org.apache.myfaces.commons.resourcehandler.resource.ResourceMeta;
41  import org.apache.myfaces.commons.resourcehandler.webapp.config.WebConfigProvider;
42  import org.apache.myfaces.commons.resourcehandler.webapp.config.WebConfigProviderFactory;
43  import org.apache.myfaces.commons.resourcehandler.webapp.config.WebRegistration;
44  import org.apache.myfaces.commons.resourcehandler.webapp.config.element.ServletRegistration;
45  import org.apache.myfaces.commons.util.ClassUtils;
46  import org.apache.myfaces.commons.util.StringUtils;
47  import org.apache.myfaces.commons.util.WebConfigParamUtils;
48  
49  public class ExtendedDefaultResourceHandlerSupport extends BaseResourceHandlerSupport
50  {
51      protected static final String CACHED_SERVLET_MAPPING =
52          ExtendedDefaultResourceHandlerSupport.class.getName() + ".CACHED_SERVLET_MAPPING";
53      
54      /**
55       * Enable or disable gzip compressions for resources served by this extended resource handler. By default is disabled (false).
56       */
57      @JSFWebConfigParam(defaultValue="false")
58      public static final String INIT_PARAM_GZIP_RESOURCES_ENABLED = "org.apache.myfaces.commons.GZIP_RESOURCES_ENABLED";
59      
60      /**
61       * Indicate the suffix used to recognize resources that should be compressed. By default is ".css .js".
62       */
63      @JSFWebConfigParam(defaultValue=".css, .js")
64      public static final String INIT_PARAM_GZIP_RESOURCES_SUFFIX = "org.apache.myfaces.commons.GZIP_RESOURCES_SUFFIX";
65      public static final String INIT_PARAM_GZIP_RESOURCES_EXTENSIONS_DEFAULT = ".css .js";
66      
67      /**
68       * Indicate if gzipped files are stored on a temporal directory to serve them later. By default is true. If this is
69       * disable, the files are compressed when they are served. 
70       */
71      @JSFWebConfigParam(defaultValue="true")
72      public static final String INIT_PARAM_CACHE_DISK_GZIP_RESOURCES = "org.apache.myfaces.commons.CACHE_DISK_GZIP_RESOURCES";
73      
74      /**
75       * Indicate the prefix that is added to each resource path that is used later to check if the request is a resource request. 
76       * 
77       * By default is /javax.faces.resource
78       */
79      @JSFWebConfigParam(defaultValue="/javax.faces.resource")
80      public static final String INIT_PARAM_EXTENDED_RESOURCE_IDENTIFIER = "org.apache.myfaces.commons.EXTENDED_RESOURCE_IDENTIFIER";
81      
82      private static final String INIT_PARAM_DELEGATE_FACES_SERVLET = "org.apache.myfaces.DELEGATE_FACES_SERVLET";
83      
84      private static Class DELEGATE_FACES_SERVLET_INTERFACE_CLASS = null;
85      
86      static 
87      {
88          try
89          {
90              DELEGATE_FACES_SERVLET_INTERFACE_CLASS = ClassUtils.classForName("org.apache.myfaces.shared_impl.webapp.webxml.DelegatedFacesServlet");
91          }
92          catch (ClassNotFoundException e)
93          {
94          }
95      }
96      
97      /**
98       * Accept-Encoding HTTP header field.
99       */
100     private static final String ACCEPT_ENCODING_HEADER = "Accept-Encoding";
101     
102     private ResourceLoader[] _resourceLoaders;
103     
104     private final boolean _gzipResourcesEnabled;
105     
106     private final String[] _gzipResourcesSuffix;
107     
108     private final boolean _cacheDiskGzipResources;
109     
110     private final boolean _developmentStage;
111     
112     private MyFacesResourcesConfig _config;
113     
114     private WebConfigProvider _webConfigProvider;
115     
116     private String _resourceIdentifier;
117     
118     public ExtendedDefaultResourceHandlerSupport()
119     {
120         super();
121         FacesContext context = FacesContext.getCurrentInstance();
122         
123         _gzipResourcesEnabled = WebConfigParamUtils.getBooleanInitParameter(context.getExternalContext(), 
124                 INIT_PARAM_GZIP_RESOURCES_ENABLED, false);
125         _gzipResourcesSuffix = StringUtils.splitShortString(
126                 WebConfigParamUtils.getStringInitParameter(context.getExternalContext(),
127                         INIT_PARAM_GZIP_RESOURCES_SUFFIX, INIT_PARAM_GZIP_RESOURCES_EXTENSIONS_DEFAULT), ' ');
128         _cacheDiskGzipResources = WebConfigParamUtils.getBooleanInitParameter(context.getExternalContext(), 
129                 INIT_PARAM_CACHE_DISK_GZIP_RESOURCES, true);
130         _developmentStage = context.isProjectStage(ProjectStage.Development);
131         
132         _resourceIdentifier = WebConfigParamUtils.getStringInitParameter(context.getExternalContext(), 
133                 INIT_PARAM_EXTENDED_RESOURCE_IDENTIFIER, ResourceHandler.RESOURCE_IDENTIFIER);
134         
135         // parse the config
136         MyFacesResourceHandlerConfigParser configParser = new MyFacesResourceHandlerConfigParser();
137         _config = configParser.parse(FacesContext.getCurrentInstance());
138 
139         _webConfigProvider = WebConfigProviderFactory.getFacesConfigResourceProviderFactory(context).
140             createWebConfigProvider(context);
141         
142         _webConfigProvider.init(context);
143         
144         // GZIPResourceLoader does some file operations to clear the cache, so this call must be done
145         // to ensure only one thread is cleaning it up (at setup time)
146         getResourceLoaders();
147     }
148     
149     public MyFacesResourcesConfig getMyFacesResourcesConfig()
150     {
151         return _config;
152     }
153     
154     public String[] getGzipResourcesSuffixes()
155     {
156         return _gzipResourcesSuffix;
157     }
158     
159     public boolean isGzipResourcesEnabled()
160     {
161         return _gzipResourcesEnabled;
162     }
163     
164     public boolean isCacheDiskGzipResources()
165     {
166         return _cacheDiskGzipResources;
167     }
168     
169     public boolean isCompressable(ResourceMeta resourceMeta)
170     {
171         if (getGzipResourcesSuffixes() != null)
172         {
173             boolean compressable = false;            
174             for (int i = 0; i < getGzipResourcesSuffixes().length; i++)
175             {
176                 if (getGzipResourcesSuffixes()[i] != null && 
177                     getGzipResourcesSuffixes()[i].length() > 0 &&
178                     resourceMeta.getResourceName().endsWith(getGzipResourcesSuffixes()[i]))
179                 {
180                     compressable = true;
181                     break;
182                 }
183             }
184             return compressable;
185         }
186         else
187         {
188             return true;
189         }
190     }
191     
192     public boolean isCompressable(Resource resource)
193     {
194         if (getGzipResourcesSuffixes() != null)
195         {
196             boolean compressable = false;            
197             for (int i = 0; i < getGzipResourcesSuffixes().length; i++)
198             {
199                 if (getGzipResourcesSuffixes()[i] != null && 
200                     getGzipResourcesSuffixes()[i].length() > 0 &&
201                     resource.getResourceName().endsWith(getGzipResourcesSuffixes()[i]))
202                 {
203                     compressable = true;
204                     break;
205                 }
206             }
207             return compressable;
208         }
209         else
210         {
211             return true;
212         }
213     }
214 
215     public boolean userAgentSupportsCompression(FacesContext facesContext)
216     {
217         String acceptEncodingHeader = facesContext.getExternalContext()
218                 .getRequestHeaderMap().get(ACCEPT_ENCODING_HEADER);
219 
220         return ResourceUtils.isGZIPEncodingAccepted(acceptEncodingHeader);
221     }
222 
223     public String calculateResourceBasePath(FacesContext facesContext)
224     {        
225         ExternalContext externalContext = facesContext.getExternalContext();      
226         String resourceBasePath = null;
227         
228         //Calculate the mapping from the current request information
229         FacesServletMapping mapping = calculateFacesServletMapping(
230                 externalContext.getRequestServletPath(),
231                 externalContext.getRequestPathInfo());
232         //FacesServletMapping mapping = getFacesServletMapping(facesContext);
233         
234         if (mapping != null)
235         {
236             if (mapping.isExtensionMapping())
237             {
238                 // Mapping using a suffix. In this case we have to strip 
239                 // the suffix. If we have a url like:
240                 // http://localhost:8080/testjsf20/javax.faces.resource/imagen.jpg.jsf?ln=dojo
241                 // 
242                 // The servlet path is /javax.faces.resource/imagen.jpg.jsf
243                 //
244                 // For obtain the resource name we have to remove the .jsf suffix and 
245                 // the prefix ResourceHandler.RESOURCE_IDENTIFIER
246                 resourceBasePath = externalContext.getRequestServletPath();
247                 int stripPoint = resourceBasePath.lastIndexOf('.');
248                 if (stripPoint > 0)
249                 {
250                     resourceBasePath = resourceBasePath.substring(0, stripPoint);
251                 }
252             }
253             else
254             {
255                 // Mapping using prefix. In this case we have to strip 
256                 // the prefix used for mapping. If we have a url like:
257                 // http://localhost:8080/testjsf20/faces/javax.faces.resource/imagen.jpg?ln=dojo
258                 //
259                 // The servlet path is /faces
260                 // and the path info is /javax.faces.resource/imagen.jpg
261                 //
262                 // For obtain the resource name we have to remove the /faces prefix and 
263                 // then the prefix ResourceHandler.RESOURCE_IDENTIFIER
264                 resourceBasePath = externalContext.getRequestPathInfo();
265             }
266             return resourceBasePath;
267         }
268         else
269         {
270             //If no mapping is detected, just return the
271             //information follows the servlet path but before
272             //the query string
273             return externalContext.getRequestPathInfo();
274         }
275     }
276     
277     protected FacesServletMapping getFacesServletMapping(FacesContext context)
278     {
279         Map<Object, Object> attributes = context.getAttributes();
280 
281         // Has the mapping already been determined during this request?
282         FacesServletMapping mapping = (FacesServletMapping) attributes.get(CACHED_SERVLET_MAPPING);
283         if (mapping == null)
284         {
285             ExternalContext externalContext = context.getExternalContext();
286             
287             FacesServletMapping calculatedMapping = calculateFacesServletMapping(
288                     externalContext.getRequestServletPath(),
289                     externalContext.getRequestPathInfo());
290             
291             if (!calculatedMapping.isPrefixMapping())
292             {
293                 // Scan the current configuration if there is a FacesServlet and if that so,
294                 // retrieve the first prefix mapping and use it.
295                 getWebConfigProvider().update(context);
296                 
297                 WebRegistration webRegistration = getWebConfigProvider().getWebRegistration(context);
298                 
299                 String prefix = getFacesServletPrefixMapping(context, webRegistration);
300                 
301                 if (prefix != null)
302                 {
303                     mapping = FacesServletMapping.createPrefixMapping(prefix);
304                 }
305                 else
306                 {
307                     mapping = calculatedMapping;
308                 }
309             }
310             else
311             {
312                 mapping = calculatedMapping;
313             }
314 
315             attributes.put(CACHED_SERVLET_MAPPING, mapping);
316         }
317         return mapping;
318     }
319     
320     private String getFacesServletPrefixMapping(FacesContext context, WebRegistration webRegistration)
321     {
322         String prefix = null;
323         
324         String delegateFacesServlet = WebConfigParamUtils.getStringInitParameter(context.getExternalContext(),
325                 INIT_PARAM_DELEGATE_FACES_SERVLET);
326         
327         for (Map.Entry<String, ? extends ServletRegistration> entry : webRegistration.getServletRegistrations().entrySet())
328         {
329             ServletRegistration registration = entry.getValue();
330             boolean facesServlet = false;
331             if (FacesServlet.class.getName().equals(registration.getClassName()))
332             {
333                 facesServlet = true;
334             }
335             else if (delegateFacesServlet != null && delegateFacesServlet.equals(registration.getClassName()))
336             {
337                 facesServlet = true;
338             }
339             else 
340             {
341                 if (DELEGATE_FACES_SERVLET_INTERFACE_CLASS != null)
342                 {
343                     try
344                     {
345                         Class servletClass = ClassUtils.classForName(registration.getClassName());
346                         if (DELEGATE_FACES_SERVLET_INTERFACE_CLASS.isAssignableFrom(servletClass));
347                         {
348                             facesServlet = true;
349                         }
350                     }
351                     catch (ClassNotFoundException e)
352                     {
353                         Log log = LogFactory.getLog(ExtendedDefaultResourceHandlerSupport.class);
354                         if (log.isTraceEnabled())
355                         {
356                             log.trace("cannot load servlet class to detect if is a FacesServlet or DelegateFacesServlet", e);
357                         }
358                     }
359                 }
360             }
361             if (facesServlet)
362             {
363                 for (String urlPattern : registration.getMappings())
364                 {
365                     String extension = urlPattern != null && urlPattern.startsWith("*.") ? urlPattern.substring(urlPattern
366                             .indexOf('.')) : null;
367                     if (extension == null)
368                     {
369                         int index = urlPattern.indexOf("/*");
370                         if (index != -1)
371                         {
372                             prefix = urlPattern.substring(0, urlPattern.indexOf("/*"));
373                         }
374                         else
375                         {
376                             prefix = urlPattern;
377                         }
378                     }
379                     else
380                     {
381                         prefix = null;
382                     }
383                     
384                     if (prefix != null)
385                     {
386                         return prefix;
387                     }
388                 }
389             }
390         }
391         return prefix;
392     }
393 
394     /**
395      * Return the resource loaders used. Note this loaders should return ExtendedResourceMeta instances.
396      */
397     public ResourceLoader[] getResourceLoaders()
398     {
399         if (_resourceLoaders == null)
400         {
401             // we should serve a compressed version of the resource, if
402             //   - ProjectStage != Development
403             //   - a compressed version is available (created in constructor)
404             //   - the user agent supports compresssion
405             if (/*!_developmentStage && */isGzipResourcesEnabled() && isCacheDiskGzipResources())
406             {
407                 _resourceLoaders = new ResourceLoader[] {
408                         new GZIPResourceLoader(new ExtendedResourceLoaderWrapper(new ExternalContextResourceLoader("/resources")), this),
409                         new GZIPResourceLoader(new ExtendedResourceLoaderWrapper(new ClassLoaderResourceLoader("META-INF/resources")), this)
410                 };
411             }
412             else
413             {
414                 _resourceLoaders = new ResourceLoader[] {
415                         new ExtendedResourceLoaderWrapper(new ExternalContextResourceLoader("/resources")),
416                         new ExtendedResourceLoaderWrapper(new ClassLoaderResourceLoader("META-INF/resources"))
417                 };
418             }
419         }
420         return _resourceLoaders;
421     }
422     
423     public WebConfigProvider getWebConfigProvider()
424     {
425         return _webConfigProvider;
426     }
427     
428     @Override
429     public String getResourceIdentifier()
430     {
431         return _resourceIdentifier;
432     }
433 
434 }