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.net.URL;
24  import java.net.URLConnection;
25  import java.util.HashMap;
26  import java.util.Map;
27  import java.util.logging.Level;
28  import java.util.logging.Logger;
29  import java.util.regex.Pattern;
30  
31  import javax.el.ELException;
32  import javax.faces.FacesException;
33  import javax.faces.FactoryFinder;
34  import javax.faces.application.ViewResource;
35  import javax.faces.context.FacesContext;
36  import javax.faces.view.facelets.Facelet;
37  import javax.faces.view.facelets.FaceletCache;
38  import javax.faces.view.facelets.FaceletCacheFactory;
39  import javax.faces.view.facelets.FaceletContext;
40  import javax.faces.view.facelets.FaceletException;
41  import javax.faces.view.facelets.FaceletHandler;
42  import javax.faces.view.facelets.ResourceResolver;
43  
44  import org.apache.myfaces.shared.resource.ResourceLoaderUtils;
45  import org.apache.myfaces.view.facelets.AbstractFaceletCache;
46  import org.apache.myfaces.view.facelets.FaceletFactory;
47  import org.apache.myfaces.view.facelets.compiler.Compiler;
48  import org.apache.myfaces.view.facelets.util.ParameterCheck;
49  
50  /**
51   * Default FaceletFactory implementation.
52   * 
53   * @author Jacob Hookom
54   * @version $Id$
55   */
56  public final class DefaultFaceletFactory extends FaceletFactory
57  {
58      private static final long INFINITE_DELAY = -1;
59      private static final long NO_CACHE_DELAY = 0;
60      
61      //protected final Log log = LogFactory.getLog(DefaultFaceletFactory.class);
62      protected final Logger log = Logger.getLogger(DefaultFaceletFactory.class.getName());
63  
64      private URL _baseUrl;
65  
66      private Compiler _compiler;
67      
68      //private Map<String, DefaultFacelet> _facelets;
69      
70      //private Map<String, DefaultFacelet> _viewMetadataFacelets;
71      
72      private Map<String, DefaultFacelet> _compositeComponentMetadataFacelets;
73  
74      private long _refreshPeriod;
75  
76      private Map<String, URL> _relativeLocations;
77  
78      private javax.faces.view.facelets.ResourceResolver _resolver;
79      private DefaultResourceResolver _defaultResolver;
80      
81      private FaceletCache<Facelet> _faceletCache;
82      private AbstractFaceletCache<Facelet> _abstractFaceletCache;
83      
84      public DefaultFaceletFactory(Compiler compiler, ResourceResolver resolver) throws IOException
85      {
86          this(compiler, resolver, -1);
87      }
88  
89      public DefaultFaceletFactory(Compiler compiler, ResourceResolver resolver, long refreshPeriod)
90      {
91          ParameterCheck.notNull("compiler", compiler);
92          ParameterCheck.notNull("resolver", resolver);
93  
94          _compiler = compiler;
95  
96          //_facelets = new HashMap<String, DefaultFacelet>();
97          
98          //_viewMetadataFacelets = new HashMap<String, DefaultFacelet>();
99          
100         _compositeComponentMetadataFacelets = new HashMap<String, DefaultFacelet>();
101 
102         _relativeLocations = new HashMap<String, URL>();
103 
104         _resolver = resolver;
105         if (_resolver instanceof DefaultResourceResolver)
106         {
107             _defaultResolver = (DefaultResourceResolver) _resolver;
108         }
109 
110         //_baseUrl = resolver.resolveUrl("/");
111 
112         _refreshPeriod = refreshPeriod < 0 ? INFINITE_DELAY : refreshPeriod * 1000;
113         
114         // facelet cache. Lookup here, because after all this is a "part" of the facelet factory implementation.
115         FaceletCacheFactory cacheFactory
116                 = (FaceletCacheFactory) FactoryFinder.getFactory(FactoryFinder.FACELET_CACHE_FACTORY);
117         _faceletCache = (FaceletCache<Facelet>) cacheFactory.getFaceletCache();
118         
119         FaceletCache.MemberFactory<Facelet> faceletFactory = new FaceletCache.MemberFactory<Facelet>()
120         {
121             public Facelet newInstance(URL url) throws IOException
122             {
123                 return _createFacelet(url);
124             }
125         };
126         FaceletCache.MemberFactory<Facelet> viewMetadataFaceletFactory = new FaceletCache.MemberFactory<Facelet>()
127         {
128             public Facelet newInstance(URL url) throws IOException
129             {
130                 return _createViewMetadataFacelet(url);
131             }
132         };
133         
134         if (_faceletCache instanceof AbstractFaceletCache)
135         {
136             _abstractFaceletCache = (AbstractFaceletCache<Facelet>) _faceletCache;
137             
138             FaceletCache.MemberFactory<Facelet> compositeComponentMetadataFaceletFactory = 
139                 new FaceletCache.MemberFactory<Facelet>()
140             {
141                 public Facelet newInstance(URL url) throws IOException
142                 {
143                     return _createCompositeComponentMetadataFacelet(url);
144                 }
145             };
146 
147             try
148             {
149                 _abstractFaceletCache.setCacheFactories(faceletFactory, 
150                         viewMetadataFaceletFactory, compositeComponentMetadataFaceletFactory);
151             } 
152             catch (Exception e)
153             {
154                 throw new FacesException(
155                     "Cannot call setMemberFactories method, Initialization of FaceletCache failed.",
156                                          e);
157             }   
158         }
159         else
160         {
161             // Note that FaceletCache.setMemberFactories method is protected, and this is the place where call
162             // this method has sense, because DefaultFaceletFactory is the responsible to create Facelet instances.
163             // The only way to do it is using reflection, and it has sense, because in this way it is possible to
164             // setup a java SecurityManager that prevents call this method (because it is protected, and to do that
165             // the code first check for "suppressAccessChecks" permission).
166             try
167             {
168                 _faceletCache.setCacheFactories(faceletFactory, viewMetadataFaceletFactory);
169             } 
170             catch (Exception e)
171             {
172                 throw new FacesException(
173                     "Cannot call setMemberFactories method, Initialization of FaceletCache failed.",
174                                          e);
175             }            
176         }
177 
178         if (log.isLoggable(Level.FINE))
179         {
180             log.fine("Using ResourceResolver: " + _resolver);
181             log.fine("Using Refresh Period: " + _refreshPeriod);
182         }
183     }
184 
185     /**
186      * Compiler this factory uses
187      * 
188      * @return final Compiler instance
189      */
190     public Compiler getCompiler()
191     {
192         return _compiler;
193     }
194     
195     private URL getBaseUrl()
196     {
197         if (_baseUrl == null)
198         {
199             _baseUrl = _resolver.resolveUrl("/");
200         }
201         return _baseUrl;
202     }
203 
204     /*
205      * (non-Javadoc)
206      * 
207      * @see org.apache.myfaces.view.facelets.FaceletFactory#getFacelet(java.lang.String)
208      */
209     @Override
210     public Facelet getFacelet(FacesContext facesContext, String uri) 
211         throws IOException, FaceletException, FacesException, ELException
212     {
213         URL url = (URL) _relativeLocations.get(uri);
214         if (url == null)
215         {
216             url = resolveURL(facesContext, getBaseUrl(), uri);
217             if (url != null)
218             {
219                 ViewResource viewResource = (ViewResource) facesContext.getAttributes().get(
220                     FaceletFactory.LAST_RESOURCE_RESOLVED);
221                 if (viewResource != null)
222                 {
223                     // If a view resource has been used to resolve a resource, the cache is in
224                     // the ResourceHandler implementation. No need to cache in _relativeLocations.
225                 }
226                 else
227                 {
228                     Map<String, URL> newLoc = new HashMap<String, URL>(_relativeLocations);
229                     newLoc.put(uri, url);
230                     _relativeLocations = newLoc;
231                 }
232             }
233             else
234             {
235                 throw new IOException("'" + uri + "' not found.");
236             }
237         }
238         return this.getFacelet(url);
239     }
240 
241     /**
242      * Create a Facelet from the passed URL. This method checks if the cached Facelet needs to be refreshed before
243      * returning. If so, uses the passed URL to build a new instance;
244      * 
245      * @param url
246      *            source url
247      * @return Facelet instance
248      * @throws IOException
249      * @throws FaceletException
250      * @throws FacesException
251      * @throws ELException
252      */
253     @Override
254     public Facelet getFacelet(URL url) throws IOException, FaceletException, FacesException, ELException
255     {
256         return _faceletCache.getFacelet(url);
257     }
258     
259     
260     @Override
261     public Facelet getFacelet(FaceletContext ctx, URL url) 
262             throws IOException, FaceletException, FacesException, ELException
263     {
264         if (_abstractFaceletCache != null)
265         {
266             return _abstractFaceletCache.getFacelet(ctx, url);
267         }
268         else
269         {
270             return _faceletCache.getFacelet(url);
271         }
272     }
273 
274     public long getRefreshPeriod()
275     {
276         return _refreshPeriod;
277     }
278 
279     /**
280      * Resolves a path based on the passed URL. If the path starts with '/', then resolve the path against
281      * {@link javax.faces.context.ExternalContext#getResource(java.lang.String)
282      * javax.faces.context.ExternalContext#getResource(java.lang.String)}. Otherwise create a new URL via
283      * {@link URL#URL(java.net.URL, java.lang.String) URL(URL, String)}.
284      * 
285      * @param source
286      *            base to resolve from
287      * @param path
288      *            relative path to the source
289      * @return resolved URL
290      * @throws IOException
291      */
292     public URL resolveURL(FacesContext context, URL source, String path) throws IOException
293     {
294         if (path.startsWith("/"))
295         {
296             context.getAttributes().put(LAST_RESOURCE_RESOLVED, null);
297             URL url = resolveURL(context, path);
298             if (url == null)
299             {
300                 throw new FileNotFoundException(path + " Not Found in ExternalContext as a Resource");
301             }
302             return url;
303         }
304         else
305         {
306             return new URL(source, path);
307         }
308     }
309 
310     /**
311      * Template method for determining if the Facelet needs to be refreshed.
312      * 
313      * @param facelet
314      *            Facelet that could have expired
315      * @return true if it needs to be refreshed
316      */
317     protected boolean needsToBeRefreshed(DefaultFacelet facelet)
318     {
319         // if set to 0, constantly reload-- nocache
320         if (_refreshPeriod == NO_CACHE_DELAY)
321         {
322             return true;
323         }
324 
325         // if set to -1, never reload
326         if (_refreshPeriod == INFINITE_DELAY)
327         {
328             return false;
329         }
330 
331         long target = facelet.getCreateTime() + _refreshPeriod;
332         if (System.currentTimeMillis() > target)
333         {
334             // Should check for file modification
335 
336             URLConnection conn = null;
337             try
338             {
339                 conn = facelet.getSource().openConnection();
340                 long lastModified = ResourceLoaderUtils.getResourceLastModified(conn);
341 
342                 return lastModified == 0 || lastModified > target;
343             }
344             catch (IOException e)
345             {
346                 throw new FaceletException("Error Checking Last Modified for " + facelet.getAlias(), e);
347             }
348             finally
349             {
350                 if (conn != null)
351                 {
352                     try
353                     {
354                         conn.getInputStream().close();
355                     }
356                     catch (Exception e)
357                     {
358                         // Ignored
359                     }
360                 }
361             }
362         }
363 
364         return false;
365     }
366 
367     /**
368      * Uses the internal Compiler reference to build a Facelet given the passed URL.
369      * 
370      * @param url
371      *            source
372      * @return a Facelet instance
373      * @throws IOException
374      * @throws FaceletException
375      * @throws FacesException
376      * @throws ELException
377      */
378     private DefaultFacelet _createFacelet(URL url) throws IOException, FaceletException, FacesException, ELException
379     {
380         if (log.isLoggable(Level.FINE))
381         {
382             log.fine("Creating Facelet for: " + url);
383         }
384 
385         String alias = "/" + _removeFirst(url.getFile(), getBaseUrl().getFile());
386         try
387         {
388             FaceletHandler h = _compiler.compile(url, alias);
389             DefaultFacelet f = new DefaultFacelet(this, _compiler.createExpressionFactory(), url, alias, alias, h);
390             return f;
391         }
392         catch (FileNotFoundException fnfe)
393         {
394             throw new FileNotFoundException("Facelet " + alias + " not found at: " + url.toExternalForm());
395         }
396     }
397     
398     /**
399      * @since 2.0
400      * @param url
401      * @return
402      * @throws IOException
403      * @throws FaceletException
404      * @throws FacesException
405      * @throws ELException
406      */
407     private DefaultFacelet _createViewMetadataFacelet(URL url)
408             throws IOException, FaceletException, FacesException, ELException
409     {
410         if (log.isLoggable(Level.FINE))
411         {
412             log.fine("Creating Facelet used to create View Metadata for: " + url);
413         }
414 
415         // The alias is used later for informative purposes, so we append 
416         // some prefix to identify later where the errors comes from.
417         String faceletId = "/"+ _removeFirst(url.getFile(), getBaseUrl().getFile());
418         String alias = "/viewMetadata" + faceletId;
419         try
420         {
421             FaceletHandler h = _compiler.compileViewMetadata(url, alias);
422             DefaultFacelet f = new DefaultFacelet(this, _compiler.createExpressionFactory(), url, alias, 
423                     faceletId, h);
424             return f;
425         }
426         catch (FileNotFoundException fnfe)
427         {
428             throw new FileNotFoundException("Facelet " + alias + " not found at: " + url.toExternalForm());
429         }
430 
431     }
432     
433     /**
434      * @since 2.0.1
435      * @param url
436      * @return
437      * @throws IOException
438      * @throws FaceletException
439      * @throws FacesException
440      * @throws ELException
441      */
442     private DefaultFacelet _createCompositeComponentMetadataFacelet(URL url)
443             throws IOException, FaceletException, FacesException, ELException
444     {
445         if (log.isLoggable(Level.FINE))
446         {
447             log.fine("Creating Facelet used to create Composite Component Metadata for: " + url);
448         }
449 
450         // The alias is used later for informative purposes, so we append 
451         // some prefix to identify later where the errors comes from.
452         String alias = "/compositeComponentMetadata/" + _removeFirst(url.getFile(), getBaseUrl().getFile());
453         try
454         {
455             FaceletHandler h = _compiler.compileCompositeComponentMetadata(url, alias);
456             DefaultFacelet f = new DefaultFacelet(this, _compiler.createExpressionFactory(), url, alias,
457                     alias, h, true);
458             return f;
459         }
460         catch (FileNotFoundException fnfe)
461         {
462             throw new FileNotFoundException("Facelet " + alias + " not found at: " + url.toExternalForm());
463         }
464     }
465 
466     /**
467      * Works in the same way as getFacelet(String uri), but redirect
468      * to getViewMetadataFacelet(URL url)
469      * @since 2.0
470      */
471     @Override
472     public Facelet getViewMetadataFacelet(FacesContext facesContext, String uri) 
473         throws IOException
474     {
475         URL url = (URL) _relativeLocations.get(uri);
476         if (url == null)
477         {
478             url = resolveURL(facesContext, getBaseUrl(), uri);
479             ViewResource viewResource = (ViewResource) facesContext.getAttributes().get(
480                 FaceletFactory.LAST_RESOURCE_RESOLVED);
481             if (url != null)
482             {
483                 if (viewResource != null)
484                 {
485                     // If a view resource has been used to resolve a resource, the cache is in
486                     // the ResourceHandler implementation. No need to cache in _relativeLocations.
487                 }
488                 else
489                 {
490                     Map<String, URL> newLoc = new HashMap<String, URL>(_relativeLocations);
491                     newLoc.put(uri, url);
492                     _relativeLocations = newLoc;
493                 }
494             }
495             else
496             {
497                 throw new IOException("'" + uri + "' not found.");
498             }
499         }
500         return this.getViewMetadataFacelet(url);
501     }
502 
503     /**
504      * @since 2.0
505      */
506     @Override
507     public Facelet getViewMetadataFacelet(URL url) throws IOException,
508             FaceletException, FacesException, ELException
509     {
510         if (_abstractFaceletCache != null)
511         {
512             return _abstractFaceletCache.getViewMetadataFacelet(url);
513         }
514         else
515         {
516             return _faceletCache.getViewMetadataFacelet(url);
517         }
518     }
519     
520     /**
521      * Works in the same way as getFacelet(String uri), but redirect
522      * to getViewMetadataFacelet(URL url)
523      * @since 2.0.1
524      */
525     @Override
526     public Facelet getCompositeComponentMetadataFacelet(FacesContext facesContext, String uri)
527         throws IOException
528     {
529         URL url = (URL) _relativeLocations.get(uri);
530         if (url == null)
531         {
532             url = resolveURL(facesContext, getBaseUrl(), uri);
533             ViewResource viewResource = (ViewResource) facesContext.getAttributes().get(
534                 FaceletFactory.LAST_RESOURCE_RESOLVED);            
535             if (url != null)
536             {
537                 if (viewResource != null)
538                 {
539                     // If a view resource has been used to resolve a resource, the cache is in
540                     // the ResourceHandler implementation. No need to cache in _relativeLocations.
541                 }
542                 else
543                 {
544                     Map<String, URL> newLoc = new HashMap<String, URL>(_relativeLocations);
545                     newLoc.put(uri, url);
546                     _relativeLocations = newLoc;
547                 }
548             }
549             else
550             {
551                 throw new IOException("'" + uri + "' not found.");
552             }
553         }
554         return this.getCompositeComponentMetadataFacelet(url);
555     }
556 
557     /**
558      * @since 2.0.1
559      */
560     @Override
561     public Facelet getCompositeComponentMetadataFacelet(URL url) throws IOException,
562             FaceletException, FacesException, ELException
563     {
564         if (_abstractFaceletCache != null)
565         {
566             return _abstractFaceletCache.getCompositeComponentMetadataFacelet(url);
567         }
568         else
569         {
570             ParameterCheck.notNull("url", url);
571 
572             String key = url.toString();
573 
574             DefaultFacelet f = _compositeComponentMetadataFacelets.get(key);
575 
576             if (f == null || this.needsToBeRefreshed(f))
577             {
578                 f = this._createCompositeComponentMetadataFacelet(url);
579                 if (_refreshPeriod != NO_CACHE_DELAY)
580                 {
581                     Map<String, DefaultFacelet> newLoc
582                             = new HashMap<String, DefaultFacelet>(_compositeComponentMetadataFacelets);
583                     newLoc.put(key, f);
584                     _compositeComponentMetadataFacelets = newLoc;
585                 }
586             }
587             return f;
588         }
589     }
590     
591     private URL resolveURL(FacesContext context, String path)
592     {
593         if (_defaultResolver != null)
594         {
595             return _defaultResolver.resolveUrl(context, path);
596         }
597         else
598         {
599             return _resolver.resolveUrl(path);
600         }
601     }
602 
603     public Facelet compileComponentFacelet(String taglibURI, String tagName, Map<String,Object> attributes)
604     {
605         FaceletHandler handler = _compiler.compileComponent(taglibURI, tagName, attributes);
606         String alias = "/component/oamf:"+tagName;
607         return new DefaultFacelet(this, _compiler.createExpressionFactory(), getBaseUrl(), alias, alias, handler);
608     }
609     
610     /**
611      * Removes the first appearance of toRemove in string.
612      *
613      * Works just like string.replaceFirst(toRemove, ""), except that toRemove
614      * is not treated as a regex (which could cause problems with filenames).
615      *
616      * @param string
617      * @param toRemove
618      * @return
619      */
620     private String _removeFirst(String string, String toRemove)
621     {
622         // do exactly what String.replaceFirst(toRemove, "") internally does,
623         // except treating the search as literal text and not as regex
624 
625         return Pattern.compile(toRemove, Pattern.LITERAL).matcher(string).replaceFirst("");
626     }
627 
628 }