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.resource;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.PushbackInputStream;
24  import java.net.URL;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.logging.Level;
31  import java.util.logging.Logger;
32  
33  import javax.el.ELContext;
34  import javax.el.ELException;
35  import javax.el.ValueExpression;
36  import javax.faces.application.ProjectStage;
37  import javax.faces.application.Resource;
38  import javax.faces.application.ResourceHandler;
39  import javax.faces.context.FacesContext;
40  import javax.faces.event.ExceptionQueuedEvent;
41  import javax.faces.event.ExceptionQueuedEventContext;
42  
43  /**
44   * Default implementation for resources
45   * 
46   * @author Leonardo Uribe (latest modification by $Author: lu4242 $)
47   * @version $Revision: 957739 $ $Date: 2010-06-24 17:27:40 -0500 (Jue, 24 Jun 2010) $
48   */
49  public class ResourceImpl extends Resource
50  {
51      public final static String JAVAX_FACES_LIBRARY_NAME = "javax.faces";
52      public final static String JSF_JS_RESOURCE_NAME = "jsf.js";
53  
54  
55      private ResourceMeta _resourceMeta;
56      private ResourceLoader _resourceLoader;
57      private ResourceHandlerSupport _resourceHandlerSupport;
58      
59      public ResourceImpl(ResourceMeta resourceMeta, 
60              ResourceLoader resourceLoader, ResourceHandlerSupport support, String contentType)
61      {
62          _resourceMeta = resourceMeta;
63          _resourceLoader = resourceLoader;
64          _resourceHandlerSupport = support;
65          setLibraryName(resourceMeta.getLibraryName());
66          setResourceName(resourceMeta.getResourceName());
67          setContentType(contentType);
68      }
69      
70      public ResourceLoader getResourceLoader()
71      {
72          return _resourceLoader;
73      }    
74      
75      @Override
76      public InputStream getInputStream() throws IOException
77      {
78          if (couldResourceContainValueExpressions())
79          {
80              return new ValueExpressionFilterInputStream(
81                      getResourceLoader().getResourceInputStream(_resourceMeta), getLibraryName(), getResourceName()); 
82          }
83          else
84          {
85              return getResourceLoader().getResourceInputStream(_resourceMeta);            
86          }
87      }
88      
89      private boolean couldResourceContainValueExpressions()
90      {
91          if (_resourceMeta.couldResourceContainValueExpressions())
92          {
93              return true;
94          }
95          else
96          {
97              //By default only css resource contain value expressions
98              String contentType = getContentType();
99      
100             return ("text/css".equals(contentType));
101         }
102     }
103 
104     @Override
105     public String getRequestPath()
106     {
107         String path;
108         if (_resourceHandlerSupport.isExtensionMapping())
109         {
110             path = _resourceHandlerSupport.getResourceIdentifier() + '/' + 
111                 getResourceName() + _resourceHandlerSupport.getMapping();
112         }
113         else
114         {
115             String mapping = _resourceHandlerSupport.getMapping(); 
116             path = _resourceHandlerSupport.getResourceIdentifier() + '/' + getResourceName();
117             path = (mapping == null) ? path : mapping + path;
118         }
119  
120         FacesContext facesContext = FacesContext.getCurrentInstance();
121         String metadata = null;
122         boolean useAmp = false;
123         if (getLibraryName() != null)
124         {
125             metadata = "?ln=" + getLibraryName();
126             path = path + metadata;
127             useAmp = true;
128             
129             if (!facesContext.isProjectStage(ProjectStage.Production)
130                     && JSF_JS_RESOURCE_NAME.equals(getResourceName()) 
131                     && JAVAX_FACES_LIBRARY_NAME.equals(getLibraryName()))
132             {
133                 // append &stage=?? for all ProjectStages except Production
134                 path = path + "&stage=" + facesContext.getApplication().getProjectStage().toString();
135             }
136         }
137         
138         return facesContext.getApplication().getViewHandler().getResourceURL(facesContext, path);
139     }
140 
141     @Override
142     public Map<String, String> getResponseHeaders()
143     {
144         FacesContext facesContext = FacesContext.getCurrentInstance();
145         
146         if (facesContext.getApplication().getResourceHandler().isResourceRequest(facesContext))
147         {
148             Map<String, String> headers = new HashMap<String, String>();
149             
150             long lastModified;
151             try
152             {
153                 lastModified = ResourceLoaderUtils.getResourceLastModified(this.getURL());
154             }
155             catch (IOException e)
156             {
157                 lastModified = -1;
158             }
159             
160             // Here we have two cases: If the file could contain EL Expressions
161             // the last modified time is the greatest value between application startup and
162             // the value from file.
163             if (this.couldResourceContainValueExpressions() &&
164                     lastModified < _resourceHandlerSupport.getStartupTime())
165             {
166                 lastModified = _resourceHandlerSupport.getStartupTime();
167             }            
168             else if (_resourceMeta instanceof AliasResourceMetaImpl &&
169                 lastModified < _resourceHandlerSupport.getStartupTime())
170             {
171                 // If the resource meta is aliased, the last modified time is the greatest 
172                 // value between application startup and the value from file.
173                 lastModified = _resourceHandlerSupport.getStartupTime();
174             }
175 
176             if (lastModified >= 0)
177             {
178                 headers.put("Last-Modified", ResourceLoaderUtils.formatDateHeader(lastModified));
179                 
180                 long expires;
181                 if (facesContext.isProjectStage(ProjectStage.Development))
182                 {
183                     // Force to expire now to prevent caching on development time.
184                     expires = System.currentTimeMillis();
185                 }
186                 else
187                 {
188                     expires = System.currentTimeMillis() + _resourceHandlerSupport.getMaxTimeExpires();
189                 }
190                 headers.put("Expires", ResourceLoaderUtils.formatDateHeader(expires));
191             }
192             
193             return headers;
194         }
195         else
196         {
197             //No need to return headers 
198             return Collections.emptyMap();
199         }
200     }
201 
202     @Override
203     public URL getURL()
204     {
205         return getResourceLoader().getResourceURL(_resourceMeta);
206     }
207 
208     @Override
209     public boolean userAgentNeedsUpdate(FacesContext context)
210     {
211         // RFC2616 says related to If-Modified-Since header the following:
212         //
213         // "... The If-Modified-Since request-header field is used with a method to 
214         // make it conditional: if the requested variant has not been modified since 
215         // the time specified in this field, an entity will not be returned from 
216         // the server; instead, a 304 (not modified) response will be returned 
217         // without any message-body..."
218         // 
219         // This method is called from ResourceHandlerImpl.handleResourceRequest and if
220         // returns false send a 304 Not Modified response.
221         
222         String ifModifiedSinceString = context.getExternalContext().getRequestHeaderMap().get("If-Modified-Since");
223         
224         if (ifModifiedSinceString == null)
225         {
226             return true;
227         }
228         
229         Long ifModifiedSince = ResourceLoaderUtils.parseDateHeader(ifModifiedSinceString);
230         
231         if (ifModifiedSince == null)
232         {
233             return true;
234         }
235         
236         Long lastModified;
237         try
238         {
239             lastModified = ResourceLoaderUtils.getResourceLastModified(this.getURL());
240         }
241         catch (IOException exception)
242         {
243             lastModified = -1L;
244         }
245         
246         if (lastModified >= 0)
247         {
248             if (this.couldResourceContainValueExpressions() &&
249                     lastModified < _resourceHandlerSupport.getStartupTime())
250             {
251                 lastModified = _resourceHandlerSupport.getStartupTime();
252             }
253             
254             // If the lastModified date is lower or equal than ifModifiedSince,
255             // the agent does not need to update.
256             // Note the lastModified time is set at milisecond precision, but when 
257             // the date is parsed and sent on ifModifiedSince, the exceding miliseconds
258             // are trimmed. So, we have to compare trimming this from the calculated
259             // lastModified time.
260             if ( (lastModified-(lastModified % 1000)) <= ifModifiedSince)
261             {
262                 return false;
263             }
264         }
265         
266         return true;
267     }
268     
269     protected ResourceHandlerSupport getResourceHandlerSupport()
270     {
271         return _resourceHandlerSupport;
272     }
273     
274     protected ResourceMeta getResourceMeta()
275     {
276         return _resourceMeta;
277     }
278 }