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