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.Externalizable;
22  import java.io.IOException;
23  import java.io.ObjectInput;
24  import java.io.ObjectOutput;
25  import java.net.URL;
26  import java.text.DateFormat;
27  import java.text.SimpleDateFormat;
28  import java.util.Collections;
29  import java.util.Collection;
30  import java.util.Date;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.WeakHashMap;
35  import java.util.logging.Level;
36  import java.util.logging.Logger;
37  
38  import javax.el.ELException;
39  import javax.el.ExpressionFactory;
40  import javax.faces.FacesException;
41  import javax.faces.application.Resource;
42  import javax.faces.component.UIComponent;
43  import javax.faces.component.UIViewRoot;
44  import javax.faces.component.UniqueIdVendor;
45  import javax.faces.context.FacesContext;
46  import javax.faces.view.facelets.FaceletContext;
47  import javax.faces.view.facelets.FaceletException;
48  import javax.faces.view.facelets.FaceletHandler;
49  import org.apache.myfaces.shared.config.MyfacesConfig;
50  
51  import org.apache.myfaces.view.facelets.AbstractFacelet;
52  import org.apache.myfaces.view.facelets.AbstractFaceletContext;
53  import org.apache.myfaces.view.facelets.FaceletCompositionContext;
54  import org.apache.myfaces.view.facelets.compiler.EncodingHandler;
55  
56  
57  /**
58   * Default Facelet implementation.
59   * 
60   * @author Jacob Hookom
61   * @version $Id: DefaultFacelet.java 1594556 2014-05-14 12:16:09Z lu4242 $
62   */
63  final class DefaultFacelet extends AbstractFacelet
64  {
65  
66      //private static final Logger log = Logger.getLogger("facelets.facelet");
67      private static final Logger log = Logger.getLogger(DefaultFacelet.class.getName());
68  
69      private final static String APPLIED_KEY = "org.apache.myfaces.view.facelets.APPLIED";
70  
71      private final String _alias;
72      
73      private final String _faceletId;
74  
75      private final ExpressionFactory _elFactory;
76  
77      private final DefaultFaceletFactory _factory;
78  
79      private final long _createTime;
80  
81      private final long _refreshPeriod;
82  
83      private final Map<String, URL> _relativePaths;
84  
85      private final FaceletHandler _root;
86  
87      private final URL _src;
88  
89      private final boolean _isBuildingCompositeComponentMetadata; 
90      
91      private final boolean _encodingHandler;
92  
93      public DefaultFacelet(DefaultFaceletFactory factory, ExpressionFactory el, URL src, String alias,
94                            String faceletId, FaceletHandler root)
95      {
96          _factory = factory;
97          _elFactory = el;
98          _src = src;
99          _root = root;
100         _alias = alias;
101         _faceletId = faceletId;
102         _createTime = System.currentTimeMillis();
103         _refreshPeriod = _factory.getRefreshPeriod();
104         _relativePaths = new WeakHashMap<String, URL>();
105         _isBuildingCompositeComponentMetadata = false;
106         _encodingHandler = (root instanceof EncodingHandler);
107     }
108 
109     public DefaultFacelet(DefaultFaceletFactory factory, ExpressionFactory el, URL src, String alias,
110             String faceletId, FaceletHandler root, boolean isBuildingCompositeComponentMetadata)
111     {
112         _factory = factory;
113         _elFactory = el;
114         _src = src;
115         _root = root;
116         _alias = alias;
117         _faceletId = faceletId;
118         _createTime = System.currentTimeMillis();
119         _refreshPeriod = _factory.getRefreshPeriod();
120         _relativePaths = new WeakHashMap<String, URL>();
121         _isBuildingCompositeComponentMetadata = isBuildingCompositeComponentMetadata;
122         _encodingHandler = (root instanceof EncodingHandler);
123     }    
124 
125     /**
126      * @see org.apache.myfaces.view.facelets.Facelet#apply(javax.faces.context.FacesContext,
127      *      javax.faces.component.UIComponent)
128      */
129     public void apply(FacesContext facesContext, UIComponent parent) throws IOException, FacesException,
130             FaceletException, ELException
131     {
132         FaceletCompositionContext myFaceletContext = null;
133         boolean faceletCompositionContextInitialized = false;
134         boolean recordUniqueIds = false;
135         myFaceletContext = FaceletCompositionContext.getCurrentInstance(facesContext);
136         if (myFaceletContext == null)
137         {
138             myFaceletContext = new FaceletCompositionContextImpl(_factory, facesContext);
139             myFaceletContext.init(facesContext);
140             faceletCompositionContextInitialized = true;
141             if (_encodingHandler && !myFaceletContext.isBuildingViewMetadata()
142                     && MyfacesConfig.getCurrentInstance(
143                     facesContext.getExternalContext()).isViewUniqueIdsCacheEnabled() && 
144                     _refreshPeriod <= 0)
145             {
146                 List<String> uniqueIdList = ((EncodingHandler)_root).getUniqueIdList();
147                 if (uniqueIdList == null)
148                 {
149                     myFaceletContext.initUniqueIdRecording();
150                     recordUniqueIds = true;
151                 }
152                 else
153                 {
154                     myFaceletContext.setUniqueIdsIterator(uniqueIdList.iterator());
155                 }
156             }
157         }
158         DefaultFaceletContext ctx = new DefaultFaceletContext(facesContext, this, myFaceletContext);
159         
160         //Set FACELET_CONTEXT_KEY on FacesContext attribute map, to 
161         //reflect the current facelet context instance
162         FaceletContext oldCtx = (FaceletContext) 
163                 facesContext.getAttributes().put(FaceletContext.FACELET_CONTEXT_KEY, ctx);
164         
165         ctx.pushPageContext(new PageContextImpl());
166         
167         try
168         {
169             // push the parent as a UniqueIdVendor to the stack here,
170             // if there is no UniqueIdVendor on the stack yet
171             boolean pushedUniqueIdVendor = false;
172             if (parent instanceof UniqueIdVendor
173                 && ctx.getFaceletCompositionContext().getUniqueIdVendorFromStack() == null)
174             {
175                 ctx.getFaceletCompositionContext().pushUniqueIdVendorToStack((UniqueIdVendor) parent);
176                 pushedUniqueIdVendor = true;
177             }
178             
179             this.refresh(parent);
180             myFaceletContext.markForDeletion(parent);
181             _root.apply(ctx, parent);
182             if (faceletCompositionContextInitialized &&
183                 parent instanceof UIViewRoot)
184             {
185                 UIComponent metadataFacet = parent.getFacet(UIViewRoot.METADATA_FACET_NAME);
186                 if (metadataFacet != null)
187                 {
188                     // Ensure metadata facet is removed from deletion, so if by some reason
189                     // is not refreshed, its content will not be removed from the component tree.
190                     // This behavior is preferred, even if the spec suggest to include it using
191                     // a trick with the template client.
192                     myFaceletContext.removeComponentForDeletion(metadataFacet);
193                 }
194                 if (myFaceletContext.isRefreshingTransientBuild())
195                 {
196                     myFaceletContext.finalizeRelocatableResourcesForDeletion((UIViewRoot) parent);
197                 }
198             }
199             myFaceletContext.finalizeForDeletion(parent);
200             this.markApplied(parent);
201             
202             // remove the UniqueIdVendor from the stack again
203             if (pushedUniqueIdVendor)
204             {
205                 ctx.getFaceletCompositionContext().popUniqueIdVendorToStack();
206             }
207         }
208         finally
209         {
210             ctx.popPageContext();
211             
212             if (faceletCompositionContextInitialized)
213             {
214                 myFaceletContext.release(facesContext);
215                 List<String> uniqueIdList = ((EncodingHandler)_root).getUniqueIdList();
216                 if (recordUniqueIds &&  uniqueIdList == null)
217                 {
218                     uniqueIdList = Collections.unmodifiableList(
219                             myFaceletContext.getUniqueIdList());
220                     ((EncodingHandler)_root).setUniqueIdList(uniqueIdList);
221                 }
222             }
223             
224             if (oldCtx != null)
225             {
226                 facesContext.getAttributes().put(FaceletContext.FACELET_CONTEXT_KEY, oldCtx);
227             }
228         }
229     }
230 
231     private final void refresh(UIComponent c)
232     {
233         if (_refreshPeriod > 0)
234         {
235 
236             // finally remove any children marked as deleted
237             int sz = c.getChildCount();
238             if (sz > 0)
239             {
240                 UIComponent cc = null;
241                 List<UIComponent> cl = c.getChildren();
242                 ApplyToken token;
243                 while (--sz >= 0)
244                 {
245                     cc = cl.get(sz);
246                     if (!cc.isTransient())
247                     {
248                         token = (ApplyToken) cc.getAttributes().get(APPLIED_KEY);
249                         if (token != null && token._time < _createTime && token._alias.equals(_alias))
250                         {
251                             if (log.isLoggable(Level.INFO))
252                             {
253                                 DateFormat df = SimpleDateFormat.getTimeInstance();
254                                 log.info("Facelet[" + _alias + "] was modified @ "
255                                         + df.format(new Date(_createTime)) + ", flushing component applied @ "
256                                         + df.format(new Date(token._time)));
257                             }
258                             cl.remove(sz);
259                         }
260                     }
261                 }
262             }
263 
264             // remove any facets marked as deleted
265             if (c.getFacetCount() > 0)
266             {
267                 Collection<UIComponent> col = c.getFacets().values();
268                 UIComponent fc;
269                 ApplyToken token;
270                 for (Iterator<UIComponent> itr = col.iterator(); itr.hasNext();)
271                 {
272                     fc = itr.next();
273                     if (!fc.isTransient())
274                     {
275                         token = (ApplyToken) fc.getAttributes().get(APPLIED_KEY);
276                         if (token != null && token._time < _createTime && token._alias.equals(_alias))
277                         {
278                             if (log.isLoggable(Level.INFO))
279                             {
280                                 DateFormat df = SimpleDateFormat.getTimeInstance();
281                                 log.info("Facelet[" + _alias + "] was modified @ "
282                                         + df.format(new Date(_createTime)) + ", flushing component applied @ "
283                                         + df.format(new Date(token._time)));
284                             }
285                             itr.remove();
286                         }
287                     }
288                 }
289             }
290         }
291     }
292 
293     private final void markApplied(UIComponent parent)
294     {
295         if (this._refreshPeriod > 0)
296         {
297             int facetCount = parent.getFacetCount();
298             int childCount = parent.getChildCount();
299             if (childCount > 0 || facetCount > 0)
300             {
301                 ApplyToken token = new ApplyToken(_alias, System.currentTimeMillis() + _refreshPeriod);
302 
303                 if (facetCount > 0)
304                 {
305                     for (UIComponent facet : parent.getFacets().values())
306                     {
307                         markApplied(token, facet);
308                     }
309                 }
310                 for (int i = 0; i < childCount; i++)
311                 {
312                     UIComponent child = parent.getChildren().get(i);
313                     markApplied(token, child);
314                 }
315             }
316         }
317     }
318 
319     private void markApplied(ApplyToken token, UIComponent c)
320     {
321         if (!c.isTransient())
322         {
323             Map<String, Object> attr = c.getAttributes();
324             if (!attr.containsKey(APPLIED_KEY))
325             {
326                 attr.put(APPLIED_KEY, token);
327             }
328         }
329     }
330 
331     /**
332      * Return the alias name for error messages and logging
333      * 
334      * @return alias name
335      */
336     public String getAlias()
337     {
338         return _alias;
339     }
340     
341     public String getFaceletId()
342     {
343         return _faceletId;
344     }
345 
346     /**
347      * Return this Facelet's ExpressionFactory instance
348      * 
349      * @return internal ExpressionFactory instance
350      */
351     public ExpressionFactory getExpressionFactory()
352     {
353         return _elFactory;
354     }
355 
356     /**
357      * The time when this Facelet was created, NOT the URL source code
358      * 
359      * @return final timestamp of when this Facelet was created
360      */
361     public long getCreateTime()
362     {
363         return _createTime;
364     }
365 
366     /**
367      * Delegates resolution to DefaultFaceletFactory reference. Also, caches URLs for relative paths.
368      * 
369      * @param path
370      *            a relative url path
371      * @return URL pointing to destination
372      * @throws IOException
373      *             if there is a problem creating the URL for the path specified
374      */
375     private URL getRelativePath(String path) throws IOException
376     {
377         URL url = (URL) _relativePaths.get(path);
378         if (url == null)
379         {
380             url = _factory.resolveURL(_src, path);
381             _relativePaths.put(path, url);
382         }
383         return url;
384     }
385 
386     /**
387      * The URL this Facelet was created from.
388      * 
389      * @return the URL this Facelet was created from
390      */
391     public URL getSource()
392     {
393         return _src;
394     }
395 
396     /**
397      * Given the passed FaceletContext, apply our child FaceletHandlers to the passed parent
398      * 
399      * @see FaceletHandler#apply(FaceletContext, UIComponent)
400      * @param ctx
401      *            the FaceletContext to use for applying our FaceletHandlers
402      * @param parent
403      *            the parent component to apply changes to
404      * @throws IOException
405      * @throws FacesException
406      * @throws FaceletException
407      * @throws ELException
408      */
409     private void include(AbstractFaceletContext ctx, UIComponent parent) throws IOException, FacesException,
410             FaceletException, ELException
411     {
412         ctx.pushPageContext(new PageContextImpl());
413         try
414         {
415             this.refresh(parent);
416             DefaultFaceletContext ctxWrapper = new DefaultFaceletContext((DefaultFaceletContext)ctx, this, false);
417             ctx.getFacesContext().getAttributes().put(FaceletContext.FACELET_CONTEXT_KEY, ctxWrapper);
418             _root.apply(ctxWrapper, parent);
419             ctx.getFacesContext().getAttributes().put(FaceletContext.FACELET_CONTEXT_KEY, ctx);
420             this.markApplied(parent);
421         }
422         finally
423         {
424             ctx.popPageContext();
425         }
426     }
427 
428     /**
429      * Used for delegation by the DefaultFaceletContext. First pulls the URL from {@link #getRelativePath(String)
430      * getRelativePath(String)}, then calls
431      * {@link #include(org.apache.myfaces.view.facelets.AbstractFaceletContext,
432      * javax.faces.component.UIComponent, java.net.URL)}.
433      * 
434      * @see FaceletContext#includeFacelet(UIComponent, String)
435      * @param ctx
436      *            FaceletContext to pass to the included Facelet
437      * @param parent
438      *            UIComponent to apply changes to
439      * @param path
440      *            relative path to the desired Facelet from the FaceletContext
441      * @throws IOException
442      * @throws FacesException
443      * @throws FaceletException
444      * @throws ELException
445      */
446     public void include(AbstractFaceletContext ctx, UIComponent parent, String path)
447             throws IOException, FacesException, FaceletException, ELException
448     {
449         URL url = this.getRelativePath(path);
450         this.include(ctx, parent, url);
451     }
452 
453     /**
454      * Grabs a DefaultFacelet from referenced DefaultFaceletFacotry
455      * 
456      * @see DefaultFaceletFactory#getFacelet(URL)
457      * @param ctx
458      *            FaceletContext to pass to the included Facelet
459      * @param parent
460      *            UIComponent to apply changes to
461      * @param url
462      *            URL source to include Facelet from
463      * @throws IOException
464      * @throws FacesException
465      * @throws FaceletException
466      * @throws ELException
467      */
468     public void include(AbstractFaceletContext ctx, UIComponent parent, URL url) throws IOException, FacesException,
469             FaceletException, ELException
470     {
471         DefaultFacelet f = (DefaultFacelet) _factory.getFacelet(url);
472         f.include(ctx, parent);
473     }
474     
475     public void applyCompositeComponent(AbstractFaceletContext ctx, UIComponent parent, Resource resource)
476             throws IOException, FacesException, FaceletException, ELException
477     {
478         // Here we are creating a facelet using the url provided by the resource.
479         // It works, but the Resource API provides getInputStream() for that. But the default
480         // implementation wraps everything that could contain ValueExpression and decode so
481         // we can't use it here.
482         //DefaultFacelet f = (DefaultFacelet) _factory.getFacelet(resource.getURL());
483         //f.apply(ctx.getFacesContext(), parent);
484         DefaultFacelet f = (DefaultFacelet) _factory.getFacelet(resource.getURL());
485         
486         ctx.pushPageContext(new PageContextImpl());
487         try
488         {
489             // push the parent as a UniqueIdVendor to the stack here,
490             // if there is no UniqueIdVendor on the stack yet
491             boolean pushedUniqueIdVendor = false;
492             FaceletCompositionContext mctx = ctx.getFaceletCompositionContext();
493             if (parent instanceof UniqueIdVendor
494                 && ctx.getFaceletCompositionContext().getUniqueIdVendorFromStack() == null)
495             {
496                 mctx.pushUniqueIdVendorToStack((UniqueIdVendor) parent);
497                 pushedUniqueIdVendor = true;
498             }
499             
500             f.refresh(parent);
501             mctx.markForDeletion(parent);
502             DefaultFaceletContext ctxWrapper = new DefaultFaceletContext( (DefaultFaceletContext)ctx, f, true);
503             //Update FACELET_CONTEXT_KEY on FacesContext attribute map, to 
504             //reflect the current facelet context instance
505             ctx.getFacesContext().getAttributes().put(FaceletContext.FACELET_CONTEXT_KEY, ctxWrapper);
506             f._root.apply(ctxWrapper, parent);
507             ctx.getFacesContext().getAttributes().put(FaceletContext.FACELET_CONTEXT_KEY, ctx);
508             mctx.finalizeForDeletion(parent);
509             f.markApplied(parent);
510             
511             // remove the UniqueIdVendor from the stack again
512             if (pushedUniqueIdVendor)
513             {
514                 ctx.getFaceletCompositionContext().popUniqueIdVendorToStack();
515             }
516         }
517         finally
518         {
519             ctx.popPageContext();
520         }
521     }
522 
523     private static class ApplyToken implements Externalizable
524     {
525         public String _alias;
526 
527         public long _time;
528 
529         public ApplyToken()
530         {
531         }
532 
533         public ApplyToken(String alias, long time)
534         {
535             _alias = alias;
536             _time = time;
537         }
538 
539         public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
540         {
541             _alias = in.readUTF();
542             _time = in.readLong();
543         }
544 
545         public void writeExternal(ObjectOutput out) throws IOException
546         {
547             out.writeUTF(_alias);
548             out.writeLong(_time);
549         }
550     }
551 
552     public String toString()
553     {
554         return _alias;
555     }
556 
557     @Override
558     public boolean isBuildingCompositeComponentMetadata()
559     {
560         return _isBuildingCompositeComponentMetadata;
561     }
562 }