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.view.facelets.impl;
20  
21  import java.io.FileNotFoundException;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.lang.reflect.Method;
25  import java.net.URL;
26  import java.net.URLConnection;
27  import java.util.HashMap;
28  import java.util.Map;
29  import java.util.logging.Level;
30  import java.util.logging.Logger;
31  import java.util.regex.Pattern;
32  
33  import javax.el.ELException;
34  import javax.faces.FacesException;
35  import javax.faces.FactoryFinder;
36  import javax.faces.view.facelets.FaceletException;
37  import javax.faces.view.facelets.FaceletHandler;
38  import javax.faces.view.facelets.ResourceResolver;
39  
40  import org.apache.myfaces.shared.resource.ResourceLoaderUtils;
41  import org.apache.myfaces.view.facelets.Facelet;
42  import org.apache.myfaces.view.facelets.FaceletFactory;
43  import org.apache.myfaces.view.facelets.compiler.Compiler;
44  import org.apache.myfaces.view.facelets.util.ParameterCheck;
45  
46  /**
47   * Default FaceletFactory implementation.
48   * 
49   * @author Jacob Hookom
50   * @version $Id: DefaultFaceletFactory.java 1341411 2012-05-22 11:19:42Z lu4242 $
51   */
52  public final class DefaultFaceletFactory extends FaceletFactory
53  {
54      private static final long INFINITE_DELAY = -1;
55      private static final long NO_CACHE_DELAY = 0;
56      
57      //protected final Log log = LogFactory.getLog(DefaultFaceletFactory.class);
58      protected final Logger log = Logger.getLogger(DefaultFaceletFactory.class.getName());
59  
60      private URL _baseUrl;
61  
62      private Compiler _compiler;
63  
64      private Map<String, DefaultFacelet> _facelets;
65      
66      private Map<String, DefaultFacelet> _viewMetadataFacelets;
67      
68      private Map<String, DefaultFacelet> _compositeComponentMetadataFacelets;
69  
70      private long _refreshPeriod;
71  
72      private Map<String, URL> _relativeLocations;
73  
74      private javax.faces.view.facelets.ResourceResolver _resolver;
75  
76      public DefaultFaceletFactory(Compiler compiler, ResourceResolver resolver) throws IOException
77      {
78          this(compiler, resolver, -1);
79      }
80  
81      public DefaultFaceletFactory(Compiler compiler, ResourceResolver resolver, long refreshPeriod)
82      {
83          ParameterCheck.notNull("compiler", compiler);
84          ParameterCheck.notNull("resolver", resolver);
85  
86          _compiler = compiler;
87  
88          _facelets = new HashMap<String, DefaultFacelet>();
89          
90          _viewMetadataFacelets = new HashMap<String, DefaultFacelet>();
91          
92          _compositeComponentMetadataFacelets = new HashMap<String, DefaultFacelet>();
93  
94          _relativeLocations = new HashMap<String, URL>();
95  
96          _resolver = resolver;
97  
98          _baseUrl = resolver.resolveUrl("/");
99  
100         _refreshPeriod = refreshPeriod < 0 ? INFINITE_DELAY : refreshPeriod * 1000;
101 
102         if (log.isLoggable(Level.FINE))
103         {
104             log.fine("Using ResourceResolver: " + _resolver);
105             log.fine("Using Refresh Period: " + _refreshPeriod);
106         }
107     }
108 
109     /**
110      * Compiler this factory uses
111      * 
112      * @return final Compiler instance
113      */
114     public Compiler getCompiler()
115     {
116         return _compiler;
117     }
118 
119     /*
120      * (non-Javadoc)
121      * 
122      * @see org.apache.myfaces.view.facelets.FaceletFactory#getFacelet(java.lang.String)
123      */
124     public Facelet getFacelet(String uri) throws IOException, FaceletException, FacesException, ELException
125     {
126         URL url = (URL) _relativeLocations.get(uri);
127         if (url == null)
128         {
129             url = resolveURL(_baseUrl, uri);
130             if (url != null)
131             {
132                 Map<String, URL> newLoc = new HashMap<String, URL>(_relativeLocations);
133                 newLoc.put(uri, url);
134                 _relativeLocations = newLoc;
135             }
136             else
137             {
138                 throw new IOException("'" + uri + "' not found.");
139             }
140         }
141         return this.getFacelet(url);
142     }
143 
144     /**
145      * Create a Facelet from the passed URL. This method checks if the cached Facelet needs to be refreshed before
146      * returning. If so, uses the passed URL to build a new instance;
147      * 
148      * @param url
149      *            source url
150      * @return Facelet instance
151      * @throws IOException
152      * @throws FaceletException
153      * @throws FacesException
154      * @throws ELException
155      */
156     public Facelet getFacelet(URL url) throws IOException, FaceletException, FacesException, ELException
157     {
158         ParameterCheck.notNull("url", url);
159         
160         String key = url.toString();
161         
162         DefaultFacelet f = _facelets.get(key);
163         
164         if (f == null || this.needsToBeRefreshed(f))
165         {
166             f = this._createFacelet(url);
167             if (_refreshPeriod != NO_CACHE_DELAY)
168             {
169                 Map<String, DefaultFacelet> newLoc = new HashMap<String, DefaultFacelet>(_facelets);
170                 newLoc.put(key, f);
171                 _facelets = newLoc;
172             }
173         }
174         
175         return f;
176     }
177 
178     public long getRefreshPeriod()
179     {
180         return _refreshPeriod;
181     }
182 
183     /**
184      * Resolves a path based on the passed URL. If the path starts with '/', then resolve the path against
185      * {@link javax.faces.context.ExternalContext#getResource(java.lang.String)
186      * javax.faces.context.ExternalContext#getResource(java.lang.String)}. Otherwise create a new URL via
187      * {@link URL#URL(java.net.URL, java.lang.String) URL(URL, String)}.
188      * 
189      * @param source
190      *            base to resolve from
191      * @param path
192      *            relative path to the source
193      * @return resolved URL
194      * @throws IOException
195      */
196     public URL resolveURL(URL source, String path) throws IOException
197     {
198         if (path.startsWith("/"))
199         {
200             URL url = _resolver.resolveUrl(path);
201             if (url == null)
202             {
203                 throw new FileNotFoundException(path + " Not Found in ExternalContext as a Resource");
204             }
205             return url;
206         }
207         else
208         {
209             return new URL(source, path);
210         }
211     }
212 
213     /**
214      * Template method for determining if the Facelet needs to be refreshed.
215      * 
216      * @param facelet
217      *            Facelet that could have expired
218      * @return true if it needs to be refreshed
219      */
220     protected boolean needsToBeRefreshed(DefaultFacelet facelet)
221     {
222         // if set to 0, constantly reload-- nocache
223         if (_refreshPeriod == NO_CACHE_DELAY)
224         {
225             return true;
226         }
227 
228         // if set to -1, never reload
229         if (_refreshPeriod == INFINITE_DELAY)
230         {
231             return false;
232         }
233 
234         long target = facelet.getCreateTime() + _refreshPeriod;
235         if (System.currentTimeMillis() > target)
236         {
237             // Should check for file modification
238 
239             try
240             {
241                 URLConnection conn = facelet.getSource().openConnection();
242                 long lastModified = ResourceLoaderUtils.getResourceLastModified(conn);
243 
244                 return lastModified == 0 || lastModified > target;
245             }
246             catch (IOException e)
247             {
248                 throw new FaceletException("Error Checking Last Modified for " + facelet.getAlias(), e);
249             }
250         }
251 
252         return false;
253     }
254 
255     /**
256      * Uses the internal Compiler reference to build a Facelet given the passed URL.
257      * 
258      * @param url
259      *            source
260      * @return a Facelet instance
261      * @throws IOException
262      * @throws FaceletException
263      * @throws FacesException
264      * @throws ELException
265      */
266     private DefaultFacelet _createFacelet(URL url) throws IOException, FaceletException, FacesException, ELException
267     {
268         if (log.isLoggable(Level.FINE))
269         {
270             log.fine("Creating Facelet for: " + url);
271         }
272 
273         String alias = "/" + _removeFirst(url.getFile(), _baseUrl.getFile());
274         try
275         {
276             FaceletHandler h = _compiler.compile(url, alias);
277             DefaultFacelet f = new DefaultFacelet(this, _compiler.createExpressionFactory(), url, alias, alias, h);
278             return f;
279         }
280         catch (FileNotFoundException fnfe)
281         {
282             throw new FileNotFoundException("Facelet " + alias + " not found at: " + url.toExternalForm());
283         }
284     }
285     
286     /**
287      * @since 2.0
288      * @param url
289      * @return
290      * @throws IOException
291      * @throws FaceletException
292      * @throws FacesException
293      * @throws ELException
294      */
295     private DefaultFacelet _createViewMetadataFacelet(URL url)
296             throws IOException, FaceletException, FacesException, ELException
297     {
298         if (log.isLoggable(Level.FINE))
299         {
300             log.fine("Creating Facelet used to create View Metadata for: " + url);
301         }
302 
303         // The alias is used later for informative purposes, so we append 
304         // some prefix to identify later where the errors comes from.
305         String faceletId = "/"+ _removeFirst(url.getFile(), _baseUrl.getFile());
306         String alias = "/viewMetadata" + faceletId;
307         try
308         {
309             FaceletHandler h = _compiler.compileViewMetadata(url, alias);
310             DefaultFacelet f = new DefaultFacelet(this, _compiler.createExpressionFactory(), url, alias, 
311                     faceletId, h);
312             return f;
313         }
314         catch (FileNotFoundException fnfe)
315         {
316             throw new FileNotFoundException("Facelet " + alias + " not found at: " + url.toExternalForm());
317         }
318 
319     }
320     
321     /**
322      * @since 2.0.1
323      * @param url
324      * @return
325      * @throws IOException
326      * @throws FaceletException
327      * @throws FacesException
328      * @throws ELException
329      */
330     private DefaultFacelet _createCompositeComponentMetadataFacelet(URL url)
331             throws IOException, FaceletException, FacesException, ELException
332     {
333         if (log.isLoggable(Level.FINE))
334         {
335             log.fine("Creating Facelet used to create Composite Component Metadata for: " + url);
336         }
337 
338         // The alias is used later for informative purposes, so we append 
339         // some prefix to identify later where the errors comes from.
340         String alias = "/compositeComponentMetadata/" + _removeFirst(url.getFile(), _baseUrl.getFile());
341         try
342         {
343             FaceletHandler h = _compiler.compileCompositeComponentMetadata(url, alias);
344             DefaultFacelet f = new DefaultFacelet(this, _compiler.createExpressionFactory(), url, alias,
345                     alias, h, true);
346             return f;
347         }
348         catch (FileNotFoundException fnfe)
349         {
350             throw new FileNotFoundException("Facelet " + alias + " not found at: " + url.toExternalForm());
351         }
352     }
353 
354     /**
355      * Works in the same way as getFacelet(String uri), but redirect
356      * to getViewMetadataFacelet(URL url)
357      * @since 2.0
358      */
359     @Override
360     public Facelet getViewMetadataFacelet(String uri) throws IOException
361     {
362         URL url = (URL) _relativeLocations.get(uri);
363         if (url == null)
364         {
365             url = resolveURL(_baseUrl, uri);
366             if (url != null)
367             {
368                 Map<String, URL> newLoc = new HashMap<String, URL>(_relativeLocations);
369                 newLoc.put(uri, url);
370                 _relativeLocations = newLoc;
371             }
372             else
373             {
374                 throw new IOException("'" + uri + "' not found.");
375             }
376         }
377         return this.getViewMetadataFacelet(url);
378     }
379 
380     /**
381      * @since 2.0
382      */
383     @Override
384     public Facelet getViewMetadataFacelet(URL url) throws IOException,
385             FaceletException, FacesException, ELException
386     {
387         ParameterCheck.notNull("url", url);
388         
389         String key = url.toString();
390         
391         DefaultFacelet f = _viewMetadataFacelets.get(key);
392         
393         if (f == null || this.needsToBeRefreshed(f))
394         {
395             f = this._createViewMetadataFacelet(url);
396             if (_refreshPeriod != NO_CACHE_DELAY)
397             {
398                 Map<String, DefaultFacelet> newLoc = new HashMap<String, DefaultFacelet>(_viewMetadataFacelets);
399                 newLoc.put(key, f);
400                 _viewMetadataFacelets = newLoc;
401             }
402         }
403         
404         return f;
405     }
406     
407     /**
408      * Works in the same way as getFacelet(String uri), but redirect
409      * to getViewMetadataFacelet(URL url)
410      * @since 2.0.1
411      */
412     @Override
413     public Facelet getCompositeComponentMetadataFacelet(String uri) throws IOException
414     {
415         URL url = (URL) _relativeLocations.get(uri);
416         if (url == null)
417         {
418             url = resolveURL(_baseUrl, uri);
419             if (url != null)
420             {
421                 Map<String, URL> newLoc = new HashMap<String, URL>(_relativeLocations);
422                 newLoc.put(uri, url);
423                 _relativeLocations = newLoc;
424             }
425             else
426             {
427                 throw new IOException("'" + uri + "' not found.");
428             }
429         }
430         return this.getCompositeComponentMetadataFacelet(url);
431     }
432 
433     /**
434      * @since 2.0.1
435      */
436     @Override
437     public Facelet getCompositeComponentMetadataFacelet(URL url) throws IOException,
438             FaceletException, FacesException, ELException
439     {
440         ParameterCheck.notNull("url", url);
441         
442         String key = url.toString();
443         
444         DefaultFacelet f = _compositeComponentMetadataFacelets.get(key);
445         
446         if (f == null || this.needsToBeRefreshed(f))
447         {
448             f = this._createCompositeComponentMetadataFacelet(url);
449             if (_refreshPeriod != NO_CACHE_DELAY)
450             {
451                 Map<String, DefaultFacelet> newLoc = new HashMap<String, DefaultFacelet>(_compositeComponentMetadataFacelets);
452                 newLoc.put(key, f);
453                 _compositeComponentMetadataFacelets = newLoc;
454             }
455         }
456         
457         return f;
458     }
459 
460     /**
461      * Removes the first appearance of toRemove in string.
462      *
463      * Works just like string.replaceFirst(toRemove, ""), except that toRemove
464      * is not treated as a regex (which could cause problems with filenames).
465      *
466      * @param string
467      * @param toRemove
468      * @return
469      */
470     private String _removeFirst(String string, String toRemove)
471     {
472         // do exactly what String.replaceFirst(toRemove, "") internally does,
473         // except treating the search as literal text and not as regex
474 
475         return Pattern.compile(toRemove, Pattern.LITERAL).matcher(string).replaceFirst("");
476     }
477 
478 }