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