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