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 1424975 2012-12-21 15:41:27Z 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         facesContext.getAttributes().put(FaceletContext.FACELET_CONTEXT_KEY, ctx);
163         
164         ctx.pushPageContext(new PageContextImpl());
165         
166         try
167         {
168             // push the parent as a UniqueIdVendor to the stack here,
169             // if there is no UniqueIdVendor on the stack yet
170             boolean pushedUniqueIdVendor = false;
171             if (parent instanceof UniqueIdVendor
172                 && ctx.getFaceletCompositionContext().getUniqueIdVendorFromStack() == null)
173             {
174                 ctx.getFaceletCompositionContext().pushUniqueIdVendorToStack((UniqueIdVendor) parent);
175                 pushedUniqueIdVendor = true;
176             }
177             
178             this.refresh(parent);
179             myFaceletContext.markForDeletion(parent);
180             _root.apply(ctx, parent);
181             if (faceletCompositionContextInitialized &&
182                 parent instanceof UIViewRoot)
183             {
184                 UIComponent metadataFacet = parent.getFacet(UIViewRoot.METADATA_FACET_NAME);
185                 if (metadataFacet != null)
186                 {
187                     // Ensure metadata facet is removed from deletion, so if by some reason
188                     // is not refreshed, its content will not be removed from the component tree.
189                     // This behavior is preferred, even if the spec suggest to include it using
190                     // a trick with the template client.
191                     myFaceletContext.removeComponentForDeletion(metadataFacet);
192                 }
193                 if (myFaceletContext.isRefreshingTransientBuild())
194                 {
195                     myFaceletContext.finalizeRelocatableResourcesForDeletion((UIViewRoot) parent);
196                 }
197             }
198             myFaceletContext.finalizeForDeletion(parent);
199             this.markApplied(parent);
200             
201             // remove the UniqueIdVendor from the stack again
202             if (pushedUniqueIdVendor)
203             {
204                 ctx.getFaceletCompositionContext().popUniqueIdVendorToStack();
205             }
206         }
207         finally
208         {
209             ctx.popPageContext();
210             
211             if (faceletCompositionContextInitialized)
212             {
213                 myFaceletContext.release(facesContext);
214                 List<String> uniqueIdList = ((EncodingHandler)_root).getUniqueIdList();
215                 if (recordUniqueIds &&  uniqueIdList == null)
216                 {
217                     uniqueIdList = Collections.unmodifiableList(
218                             myFaceletContext.getUniqueIdList());
219                     ((EncodingHandler)_root).setUniqueIdList(uniqueIdList);
220                 }
221             }
222         }
223     }
224 
225     private final void refresh(UIComponent c)
226     {
227         if (_refreshPeriod > 0)
228         {
229 
230             // finally remove any children marked as deleted
231             int sz = c.getChildCount();
232             if (sz > 0)
233             {
234                 UIComponent cc = null;
235                 List<UIComponent> cl = c.getChildren();
236                 ApplyToken token;
237                 while (--sz >= 0)
238                 {
239                     cc = cl.get(sz);
240                     if (!cc.isTransient())
241                     {
242                         token = (ApplyToken) cc.getAttributes().get(APPLIED_KEY);
243                         if (token != null && token._time < _createTime && token._alias.equals(_alias))
244                         {
245                             if (log.isLoggable(Level.INFO))
246                             {
247                                 DateFormat df = SimpleDateFormat.getTimeInstance();
248                                 log.info("Facelet[" + _alias + "] was modified @ "
249                                         + df.format(new Date(_createTime)) + ", flushing component applied @ "
250                                         + df.format(new Date(token._time)));
251                             }
252                             cl.remove(sz);
253                         }
254                     }
255                 }
256             }
257 
258             // remove any facets marked as deleted
259             if (c.getFacetCount() > 0)
260             {
261                 Collection<UIComponent> col = c.getFacets().values();
262                 UIComponent fc;
263                 ApplyToken token;
264                 for (Iterator<UIComponent> itr = col.iterator(); itr.hasNext();)
265                 {
266                     fc = itr.next();
267                     if (!fc.isTransient())
268                     {
269                         token = (ApplyToken) fc.getAttributes().get(APPLIED_KEY);
270                         if (token != null && token._time < _createTime && token._alias.equals(_alias))
271                         {
272                             if (log.isLoggable(Level.INFO))
273                             {
274                                 DateFormat df = SimpleDateFormat.getTimeInstance();
275                                 log.info("Facelet[" + _alias + "] was modified @ "
276                                         + df.format(new Date(_createTime)) + ", flushing component applied @ "
277                                         + df.format(new Date(token._time)));
278                             }
279                             itr.remove();
280                         }
281                     }
282                 }
283             }
284         }
285     }
286 
287     private final void markApplied(UIComponent parent)
288     {
289         if (this._refreshPeriod > 0)
290         {
291             int facetCount = parent.getFacetCount();
292             int childCount = parent.getChildCount();
293             if (childCount > 0 || facetCount > 0)
294             {
295                 ApplyToken token = new ApplyToken(_alias, System.currentTimeMillis() + _refreshPeriod);
296 
297                 if (facetCount > 0)
298                 {
299                     for (UIComponent facet : parent.getFacets().values())
300                     {
301                         markApplied(token, facet);
302                     }
303                 }
304                 for (int i = 0; i < childCount; i++)
305                 {
306                     UIComponent child = parent.getChildren().get(i);
307                     markApplied(token, child);
308                 }
309             }
310         }
311     }
312 
313     private void markApplied(ApplyToken token, UIComponent c)
314     {
315         if (!c.isTransient())
316         {
317             Map<String, Object> attr = c.getAttributes();
318             if (!attr.containsKey(APPLIED_KEY))
319             {
320                 attr.put(APPLIED_KEY, token);
321             }
322         }
323     }
324 
325     /**
326      * Return the alias name for error messages and logging
327      * 
328      * @return alias name
329      */
330     public String getAlias()
331     {
332         return _alias;
333     }
334     
335     public String getFaceletId()
336     {
337         return _faceletId;
338     }
339 
340     /**
341      * Return this Facelet's ExpressionFactory instance
342      * 
343      * @return internal ExpressionFactory instance
344      */
345     public ExpressionFactory getExpressionFactory()
346     {
347         return _elFactory;
348     }
349 
350     /**
351      * The time when this Facelet was created, NOT the URL source code
352      * 
353      * @return final timestamp of when this Facelet was created
354      */
355     public long getCreateTime()
356     {
357         return _createTime;
358     }
359 
360     /**
361      * Delegates resolution to DefaultFaceletFactory reference. Also, caches URLs for relative paths.
362      * 
363      * @param path
364      *            a relative url path
365      * @return URL pointing to destination
366      * @throws IOException
367      *             if there is a problem creating the URL for the path specified
368      */
369     private URL getRelativePath(String path) throws IOException
370     {
371         URL url = (URL) _relativePaths.get(path);
372         if (url == null)
373         {
374             url = _factory.resolveURL(_src, path);
375             _relativePaths.put(path, url);
376         }
377         return url;
378     }
379 
380     /**
381      * The URL this Facelet was created from.
382      * 
383      * @return the URL this Facelet was created from
384      */
385     public URL getSource()
386     {
387         return _src;
388     }
389 
390     /**
391      * Given the passed FaceletContext, apply our child FaceletHandlers to the passed parent
392      * 
393      * @see FaceletHandler#apply(FaceletContext, UIComponent)
394      * @param ctx
395      *            the FaceletContext to use for applying our FaceletHandlers
396      * @param parent
397      *            the parent component to apply changes to
398      * @throws IOException
399      * @throws FacesException
400      * @throws FaceletException
401      * @throws ELException
402      */
403     private void include(AbstractFaceletContext ctx, UIComponent parent) throws IOException, FacesException,
404             FaceletException, ELException
405     {
406         ctx.pushPageContext(new PageContextImpl());
407         try
408         {
409             this.refresh(parent);
410             DefaultFaceletContext ctxWrapper = new DefaultFaceletContext((DefaultFaceletContext)ctx, this, false);
411             ctx.getFacesContext().getAttributes().put(FaceletContext.FACELET_CONTEXT_KEY, ctxWrapper);
412             _root.apply(ctxWrapper, parent);
413             ctx.getFacesContext().getAttributes().put(FaceletContext.FACELET_CONTEXT_KEY, ctx);
414             this.markApplied(parent);
415         }
416         finally
417         {
418             ctx.popPageContext();
419         }
420     }
421 
422     /**
423      * Used for delegation by the DefaultFaceletContext. First pulls the URL from {@link #getRelativePath(String)
424      * getRelativePath(String)}, then calls
425      * {@link #include(org.apache.myfaces.view.facelets.AbstractFaceletContext,
426      * javax.faces.component.UIComponent, java.net.URL)}.
427      * 
428      * @see FaceletContext#includeFacelet(UIComponent, String)
429      * @param ctx
430      *            FaceletContext to pass to the included Facelet
431      * @param parent
432      *            UIComponent to apply changes to
433      * @param path
434      *            relative path to the desired Facelet from the FaceletContext
435      * @throws IOException
436      * @throws FacesException
437      * @throws FaceletException
438      * @throws ELException
439      */
440     public void include(AbstractFaceletContext ctx, UIComponent parent, String path)
441             throws IOException, FacesException, FaceletException, ELException
442     {
443         URL url = this.getRelativePath(path);
444         this.include(ctx, parent, url);
445     }
446 
447     /**
448      * Grabs a DefaultFacelet from referenced DefaultFaceletFacotry
449      * 
450      * @see DefaultFaceletFactory#getFacelet(URL)
451      * @param ctx
452      *            FaceletContext to pass to the included Facelet
453      * @param parent
454      *            UIComponent to apply changes to
455      * @param url
456      *            URL source to include Facelet from
457      * @throws IOException
458      * @throws FacesException
459      * @throws FaceletException
460      * @throws ELException
461      */
462     public void include(AbstractFaceletContext ctx, UIComponent parent, URL url) throws IOException, FacesException,
463             FaceletException, ELException
464     {
465         DefaultFacelet f = (DefaultFacelet) _factory.getFacelet(url);
466         f.include(ctx, parent);
467     }
468     
469     public void applyCompositeComponent(AbstractFaceletContext ctx, UIComponent parent, Resource resource)
470             throws IOException, FacesException, FaceletException, ELException
471     {
472         // Here we are creating a facelet using the url provided by the resource.
473         // It works, but the Resource API provides getInputStream() for that. But the default
474         // implementation wraps everything that could contain ValueExpression and decode so
475         // we can't use it here.
476         //DefaultFacelet f = (DefaultFacelet) _factory.getFacelet(resource.getURL());
477         //f.apply(ctx.getFacesContext(), parent);
478         DefaultFacelet f = (DefaultFacelet) _factory.getFacelet(resource.getURL());
479         
480         ctx.pushPageContext(new PageContextImpl());
481         try
482         {
483             // push the parent as a UniqueIdVendor to the stack here,
484             // if there is no UniqueIdVendor on the stack yet
485             boolean pushedUniqueIdVendor = false;
486             FaceletCompositionContext mctx = ctx.getFaceletCompositionContext();
487             if (parent instanceof UniqueIdVendor
488                 && ctx.getFaceletCompositionContext().getUniqueIdVendorFromStack() == null)
489             {
490                 mctx.pushUniqueIdVendorToStack((UniqueIdVendor) parent);
491                 pushedUniqueIdVendor = true;
492             }
493             
494             this.refresh(parent);
495             mctx.markForDeletion(parent);
496             DefaultFaceletContext ctxWrapper = new DefaultFaceletContext( (DefaultFaceletContext)ctx, f, true);
497             //Update FACELET_CONTEXT_KEY on FacesContext attribute map, to 
498             //reflect the current facelet context instance
499             ctx.getFacesContext().getAttributes().put(FaceletContext.FACELET_CONTEXT_KEY, ctxWrapper);
500             f._root.apply(ctxWrapper, parent);
501             ctx.getFacesContext().getAttributes().put(FaceletContext.FACELET_CONTEXT_KEY, ctx);
502             mctx.finalizeForDeletion(parent);
503             this.markApplied(parent);
504             
505             // remove the UniqueIdVendor from the stack again
506             if (pushedUniqueIdVendor)
507             {
508                 ctx.getFaceletCompositionContext().popUniqueIdVendorToStack();
509             }
510         }
511         finally
512         {
513             ctx.popPageContext();
514         }
515     }
516 
517     private static class ApplyToken implements Externalizable
518     {
519         public String _alias;
520 
521         public long _time;
522 
523         public ApplyToken()
524         {
525         }
526 
527         public ApplyToken(String alias, long time)
528         {
529             _alias = alias;
530             _time = time;
531         }
532 
533         public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
534         {
535             _alias = in.readUTF();
536             _time = in.readLong();
537         }
538 
539         public void writeExternal(ObjectOutput out) throws IOException
540         {
541             out.writeUTF(_alias);
542             out.writeLong(_time);
543         }
544     }
545 
546     public String toString()
547     {
548         return _alias;
549     }
550 
551     @Override
552     public boolean isBuildingCompositeComponentMetadata()
553     {
554         return _isBuildingCompositeComponentMetadata;
555     }
556 }