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