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.tag.jsf;
20  
21  import java.io.IOException;
22  import java.util.Collection;
23  import java.util.Iterator;
24  import java.util.Locale;
25  import java.util.Map;
26  
27  import javax.faces.FacesException;
28  import javax.faces.component.NamingContainer;
29  import javax.faces.component.UIComponent;
30  import javax.faces.component.UINamingContainer;
31  import javax.faces.component.UIPanel;
32  import javax.faces.component.UIViewRoot;
33  import javax.faces.component.UniqueIdVendor;
34  import javax.faces.context.FacesContext;
35  import javax.faces.view.facelets.FaceletContext;
36  import javax.faces.view.facelets.TagAttribute;
37  import javax.faces.view.facelets.TagAttributeException;
38  
39  import org.apache.myfaces.shared.config.MyfacesConfig;
40  import org.apache.myfaces.view.facelets.ComponentState;
41  import org.apache.myfaces.view.facelets.DefaultFaceletsStateManagementStrategy;
42  import org.apache.myfaces.view.facelets.FaceletCompositionContext;
43  import org.apache.myfaces.view.facelets.FaceletViewDeclarationLanguage;
44  
45  /**
46   * 
47   * @author Jacob Hookom
48   * @version $Id: ComponentSupport.java 1470767 2013-04-23 01:07:29Z lu4242 $
49   */
50  public final class ComponentSupport
51  {
52  
53      private final static String MARK_DELETED = "oam.vf.MARK_DELETED";
54      public final static String MARK_CREATED = "oam.vf.MARK_ID";
55      
56      /**
57       * The UIPanel components, which are dynamically generated to serve as a container for
58       * facets with multiple non panel children, are marked with this attribute.
59       * This constant is duplicate in javax.faces.webapp.UIComponentClassicTagBase
60       */
61      public final static String FACET_CREATED_UIPANEL_MARKER = "oam.vf.createdUIPanel";
62  
63      /**
64       * Special myfaces core marker to indicate the component is handled by a facelet tag handler,
65       * so its creation is not handled by user programatically and PSS remove listener should
66       * not register it when a remove happens.
67       */
68      public final static String COMPONENT_ADDED_BY_HANDLER_MARKER = "oam.vf.addedByHandler";
69  
70      /**
71       * The key under the facelet state map is stored
72       */
73      public final static String FACELET_STATE_INSTANCE = "oam.FACELET_STATE_INSTANCE";
74  
75      /**
76       * Used in conjunction with markForDeletion where any UIComponent marked will be removed.
77       * 
78       * @deprecated use FaceletCompositionContext.finalizeForDeletion
79       * @param component
80       *            UIComponent to finalize
81       */
82      @Deprecated
83      public static void finalizeForDeletion(UIComponent component)
84      {
85          // remove any existing marks of deletion
86          component.getAttributes().remove(MARK_DELETED);
87  
88          // finally remove any children marked as deleted
89          if (component.getChildCount() > 0)
90          {
91              for (Iterator<UIComponent> iter = component.getChildren().iterator(); iter.hasNext();)
92              {
93                  UIComponent child = iter.next();
94                  if (child.getAttributes().containsKey(MARK_DELETED))
95                  {
96                      iter.remove();
97                  }
98              }
99          }
100 
101         // remove any facets marked as deleted
102         if (component.getFacetCount() > 0)
103         {
104             Map<String, UIComponent> facets = component.getFacets();
105             Collection<UIComponent> col = facets.values();
106             for (Iterator<UIComponent> itr = col.iterator(); itr.hasNext();)
107             {
108                 UIComponent fc = itr.next();
109                 if (fc.getAttributes().containsKey(MARK_DELETED))
110                 {
111                     itr.remove();
112                 }
113             }
114         }
115     }
116 
117     /**
118      * A lighter-weight version of UIComponent's findChild.
119      * 
120      * @param parent
121      *            parent to start searching from
122      * @param id
123      *            to match to
124      * @return UIComponent found or null
125      */
126     public static UIComponent findChild(UIComponent parent, String id)
127     {
128         int childCount = parent.getChildCount();
129         if (childCount > 0)
130         {
131             for (int i = 0; i < childCount; i++)
132             {
133                 UIComponent child = parent.getChildren().get(i);
134                 if (id.equals(child.getId()))
135                 {
136                     return child;
137                 }
138             }
139         }
140 
141         return null;
142     }
143 
144     /**
145      * By TagId, find Child
146      * 
147      * @param parent
148      * @param id
149      * @return
150      */
151     public static UIComponent findChildByTagId(UIComponent parent, String id)
152     {
153         Iterator<UIComponent> itr = null;
154         if (parent.getChildCount() > 0)
155         {
156             for (int i = 0, childCount = parent.getChildCount(); i < childCount; i ++)
157             {
158                 UIComponent child = parent.getChildren().get(i);
159                 if (id.equals(child.getAttributes().get(MARK_CREATED)))
160                 {
161                     return child;
162                 }
163             }
164         }
165         if (parent.getFacetCount() > 0)
166         {
167             itr = parent.getFacets().values().iterator();
168             while (itr.hasNext())
169             {
170                 UIComponent facet = itr.next();
171                 // check if this is a dynamically generated UIPanel
172                 if (Boolean.TRUE.equals(facet.getAttributes()
173                              .get(FACET_CREATED_UIPANEL_MARKER)))
174                 {
175                     // only check the children and facets of the panel
176                     if (facet.getChildCount() > 0)
177                     {
178                         for (int i = 0, childCount = facet.getChildCount(); i < childCount; i ++)
179                         {
180                             UIComponent child = facet.getChildren().get(i);
181                             if (id.equals(child.getAttributes().get(MARK_CREATED)))
182                             {
183                                 return child;
184                             }
185                         }
186                     }
187                     if (facet.getFacetCount() > 0)
188                     {
189                         Iterator<UIComponent> itr2 = facet.getFacets().values().iterator();
190                         while (itr2.hasNext())
191                         {
192                             UIComponent child = itr2.next();
193                             if (id.equals(child.getAttributes().get(MARK_CREATED)))
194                             {
195                                 return child;
196                             }
197                         }
198                     }
199                 }
200                 else if (id.equals(facet.getAttributes().get(MARK_CREATED)))
201                 {
202                     return facet;
203                 }
204             }
205         }
206 
207         return null;
208     }
209 
210     /**
211      * According to JSF 1.2 tag specs, this helper method will use the TagAttribute passed in determining the Locale
212      * intended.
213      * 
214      * @param ctx
215      *            FaceletContext to evaluate from
216      * @param attr
217      *            TagAttribute representing a Locale
218      * @return Locale found
219      * @throws TagAttributeException
220      *             if the Locale cannot be determined
221      */
222     public static Locale getLocale(FaceletContext ctx, TagAttribute attr) throws TagAttributeException
223     {
224         Object obj = attr.getObject(ctx);
225         if (obj instanceof Locale)
226         {
227             return (Locale) obj;
228         }
229         if (obj instanceof String)
230         {
231             String s = (String) obj;
232             if (s.length() == 2)
233             {
234                 return new Locale(s);
235             }
236 
237             if (s.length() == 5)
238             {
239                 return new Locale(s.substring(0, 2), s.substring(3, 5).toUpperCase());
240             }
241 
242             if (s.length() >= 7)
243             {
244                 return new Locale(s.substring(0, 2), s.substring(3, 5).toUpperCase(), s.substring(6, s.length()));
245             }
246 
247             throw new TagAttributeException(attr, "Invalid Locale Specified: " + s);
248         }
249         else
250         {
251             throw new TagAttributeException(attr, "Attribute did not evaluate to a String or Locale: " + obj);
252         }
253     }
254 
255     /**
256      * Tries to walk up the parent to find the UIViewRoot, if not found, then go to FaceletContext's FacesContext for
257      * the view root.
258      * 
259      * @param ctx
260      *            FaceletContext
261      * @param parent
262      *            UIComponent to search from
263      * @return UIViewRoot instance for this evaluation
264      */
265     public static UIViewRoot getViewRoot(FaceletContext ctx, UIComponent parent)
266     {
267         UIComponent c = parent;
268         do
269         {
270             if (c instanceof UIViewRoot)
271             {
272                 return (UIViewRoot) c;
273             }
274             else
275             {
276                 c = c.getParent();
277             }
278         } while (c != null);
279 
280         return ctx.getFacesContext().getViewRoot();
281     }
282     
283     /**
284      * Marks all direct children and Facets with an attribute for deletion.
285      * 
286      * @deprecated use FaceletCompositionContext.markForDeletion
287      * @see #finalizeForDeletion(UIComponent)
288      * @param component
289      *            UIComponent to mark
290      */
291     @Deprecated
292     public static void markForDeletion(UIComponent component)
293     {
294         // flag this component as deleted
295         component.getAttributes().put(MARK_DELETED, Boolean.TRUE);
296 
297         Iterator<UIComponent> iter = component.getFacetsAndChildren();
298         while (iter.hasNext())
299         {
300             UIComponent child = iter.next();
301             if (child.getAttributes().containsKey(MARK_CREATED))
302             {
303                 child.getAttributes().put(MARK_DELETED, Boolean.TRUE);
304             }
305         }
306     }
307 
308     public static void encodeRecursive(FacesContext context, UIComponent toRender) throws IOException, FacesException
309     {
310         if (toRender.isRendered())
311         {
312             toRender.encodeBegin(context);
313             
314             if (toRender.getRendersChildren())
315             {
316                 toRender.encodeChildren(context);
317             }
318             else if (toRender.getChildCount() > 0)
319             {
320                 for (int i = 0, childCount = toRender.getChildCount(); i < childCount; i++)
321                 {
322                     UIComponent child = toRender.getChildren().get(i);
323                     encodeRecursive(context, child);
324                 }
325             }
326             
327             toRender.encodeEnd(context);
328         }
329     }
330 
331     public static void removeTransient(UIComponent component)
332     {
333         if (component.getChildCount() > 0)
334         {
335             for (Iterator<UIComponent> itr = component.getChildren().iterator(); itr.hasNext();)
336             {
337                 UIComponent child = itr.next();
338                 if (child.isTransient())
339                 {
340                     itr.remove();
341                 }
342                 else
343                 {
344                     removeTransient(child);
345                 }
346             }
347         }
348         
349         if (component.getFacetCount() > 0)
350         {
351             Map<String, UIComponent> facets = component.getFacets();
352             for (Iterator<UIComponent> itr = facets.values().iterator(); itr.hasNext();)
353             {
354                 UIComponent facet = itr.next();
355                 if (facet.isTransient())
356                 {
357                     itr.remove();
358                 }
359                 else
360                 {
361                     removeTransient(facet);
362                 }
363             }
364         }
365     }
366 
367     /**
368      * Determine if the passed component is not null and if it's new to the tree. This operation can be used for
369      * determining if attributes should be wired to the component.
370      * 
371      * @deprecated use ComponentHandler.isNew
372      * @param component
373      *            the component you wish to modify
374      * @return true if it's new
375      */
376     @Deprecated
377     public static boolean isNew(UIComponent component)
378     {
379         return component != null && component.getParent() == null;
380     }
381     
382     /**
383      * Create a new UIPanel for the use as a dynamically 
384      * created container for multiple children in a facet.
385      * Duplicate in javax.faces.webapp.UIComponentClassicTagBase.
386      * @param facesContext
387      * @return
388      */
389     private static UIComponent createFacetUIPanel(FaceletContext ctx, UIComponent parent, String facetName)
390     {
391         FacesContext facesContext = ctx.getFacesContext();
392         UIComponent panel = facesContext.getApplication().createComponent(facesContext, UIPanel.COMPONENT_TYPE, null);
393         
394         // The panel created by this method is special. To be restored properly and do not
395         // create duplicate ids or any other unwanted conflicts, it requires an unique id.
396         // This code is usually called when more than one component is added to a facet and
397         // it is necessary to create a shared container.
398         // Use FaceletCompositionContext.generateUniqueComponentId() is not possible, because
399         // <c:if> blocks inside a facet will make component ids unstable. Use UniqueIdVendor
400         // is feasible but also will be affected by <c:if> blocks inside a facet.
401         // The only solution that will generate real unique ids is use the parent id and the
402         // facet name and derive an unique id that cannot be generated by SectionUniqueIdCounter,
403         // doing the same trick as with metadata: use a double __ and add a prefix (f).
404         // Note this id will never be printed into the response, because this is just a container.
405         FaceletCompositionContext mctx = FaceletCompositionContext.getCurrentInstance(ctx);
406         UniqueIdVendor uniqueIdVendor = mctx.getUniqueIdVendorFromStack();
407         if (uniqueIdVendor == null)
408         {
409             uniqueIdVendor = ComponentSupport.getViewRoot(ctx, parent);
410         }
411         if (uniqueIdVendor != null)
412         {
413             // UIViewRoot implements UniqueIdVendor, so there is no need to cast to UIViewRoot
414             // and call createUniqueId(). See ComponentTagHandlerDelegate
415             int index = facetName.indexOf('.');
416             String cleanFacetName = facetName;
417             if (index >= 0)
418             {
419                 cleanFacetName = facetName.replace('.', '_');
420             }
421             panel.setId(uniqueIdVendor.createUniqueId(facesContext, 
422                     mctx.getSharedStringBuilder()
423                       .append(parent.getId())
424                       .append("__f_")
425                       .append(cleanFacetName).toString()));
426         }
427         panel.getAttributes().put(FACET_CREATED_UIPANEL_MARKER, Boolean.TRUE);
428         panel.getAttributes().put(ComponentSupport.COMPONENT_ADDED_BY_HANDLER_MARKER, Boolean.TRUE);
429         return panel;
430     }
431     
432     public static void addFacet(FaceletContext ctx, UIComponent parent, UIComponent c, String facetName)
433     {
434         // facets now can have multiple children and the direct
435         // child of a facet is always an UIPanel (since 2.0)
436         UIComponent facet = parent.getFacets().get(facetName);
437         if (facet == null)
438         {
439             //Just set it directly like  before
440             parent.getFacets().put(facetName, c);
441         }
442         else if (!(facet instanceof UIPanel))
443         {
444             // there is a facet, but it is not an instance of UIPanel
445             UIComponent child = facet;
446             facet = createFacetUIPanel(ctx, parent, facetName);
447             facet.getChildren().add(child);
448             facet.getChildren().add(c);
449             parent.getFacets().put(facetName, facet);
450         }
451         else
452         {
453             // we have a facet, which is an instance of UIPanel at this point
454             // check if it is a facet marked UIPanel
455             if (Boolean.TRUE.equals(facet.getAttributes().get(FACET_CREATED_UIPANEL_MARKER)))
456             {
457                 facet.getChildren().add(c);
458             }
459             else
460             {
461                 // the facet is an instance of UIPanel, but it is not marked,
462                 // so we have to create a new UIPanel and store this one in it
463                 UIComponent oldPanel = facet;
464                 facet = createFacetUIPanel(ctx, parent, facetName);
465                 facet.getChildren().add(oldPanel);
466                 facet.getChildren().add(c);
467                 parent.getFacets().put(facetName, facet);
468             }
469         }
470     }
471     
472     public static void removeFacet(FaceletContext ctx, UIComponent parent, UIComponent c, String facetName)
473     {
474         UIComponent facet = parent.getFacet(facetName);
475         if (Boolean.TRUE.equals(facet.getAttributes().get(FACET_CREATED_UIPANEL_MARKER)))
476         {
477             facet.getChildren().remove(c);
478         }
479         else
480         {
481             parent.getFacets().remove(facetName);
482         }
483     }
484 
485     public static void markComponentToRestoreFully(FacesContext context, UIComponent component)
486     {
487         if (MyfacesConfig.getCurrentInstance(context.getExternalContext()).isRefreshTransientBuildOnPSSPreserveState())
488         {
489             component.getAttributes().put(DefaultFaceletsStateManagementStrategy.COMPONENT_ADDED_AFTER_BUILD_VIEW,
490                                           ComponentState.REMOVE_ADD);
491         }
492         //component.subscribeToEvent(PostAddToViewEvent.class, new RestoreComponentFullyListener());
493         if (FaceletViewDeclarationLanguage.isRefreshTransientBuildOnPSSAuto(context))
494         {
495             FaceletViewDeclarationLanguage.cleanTransientBuildOnRestore(context);
496         }
497     }
498     
499     public static UIComponent findComponentChildOrFacetFrom(FacesContext facesContext, UIComponent parent, String expr)
500     {
501         final char separatorChar = UINamingContainer.getSeparatorChar(facesContext);
502         int separator = expr.indexOf(separatorChar);
503         if (separator == -1)
504         {
505             return ComponentSupport.findComponentChildOrFacetFrom(
506                     parent, expr, null);
507         }
508         else
509         {
510             return ComponentSupport.findComponentChildOrFacetFrom(
511                     parent, expr.substring(0,separator), expr);
512         }
513     }
514     
515     public static UIComponent findComponentChildOrFacetFrom(UIComponent parent, String id, String innerExpr)
516     {
517         if (parent.getFacetCount() > 0)
518         {
519             for (UIComponent facet : parent.getFacets().values())
520             {
521                 if (id.equals(facet.getId()))
522                 {
523                     if (innerExpr == null)
524                     {
525                         return facet;
526                     }
527                     else if (facet instanceof NamingContainer)
528                     {
529                         UIComponent find = facet.findComponent(innerExpr);
530                         if (find != null)
531                         {
532                             return find;
533                         }
534                     }
535                 }
536                 else if (!(facet instanceof NamingContainer))
537                 {
538                     UIComponent find = findComponentChildOrFacetFrom(facet, id, innerExpr);
539                     if (find != null)
540                     {
541                         return find;
542                     }
543                 }
544             }
545         }
546         if (parent.getChildCount() > 0)
547         {
548             for (int i = 0, childCount = parent.getChildCount(); i < childCount; i++)
549             {
550                 UIComponent child = parent.getChildren().get(i);
551                 if (id.equals(child.getId()))
552                 {
553                     if (innerExpr == null)
554                     {
555                         return child;
556                     }
557                     else if (child instanceof NamingContainer)
558                     {
559                         UIComponent find = child.findComponent(innerExpr);
560                         if (find != null)
561                         {
562                             return find;
563                         }
564                     }
565                 }
566                 else if (!(child instanceof NamingContainer))
567                 {
568                     UIComponent find = findComponentChildOrFacetFrom(child, id, innerExpr);
569                     if (find != null)
570                     {
571                         return find;
572                     }
573                 }
574             }
575         }
576         return null;
577     }
578     
579     public static String getFindComponentExpression(FacesContext facesContext, UIComponent component)
580     {
581         char separatorChar = UINamingContainer.getSeparatorChar(facesContext);
582         UIComponent parent = component.getParent();
583         StringBuilder sb = new StringBuilder();
584         sb.append(component.getId());
585         while (parent != null)
586         {
587             if (parent instanceof NamingContainer)
588             {
589                 sb.insert(0, separatorChar);
590                 sb.insert(0, parent.getId());
591             }
592             parent = parent.getParent();
593         }
594         return sb.toString();
595     }
596     
597     public static Object restoreInitialTagState(FaceletContext ctx, FaceletCompositionContext fcc,
598                                                 UIComponent parent, String uniqueId)
599     {
600         Object value = null;
601         //if (fcc.isUsingPSSOnThisView() &&
602         //    PhaseId.RESTORE_VIEW.equals(ctx.getFacesContext().getCurrentPhaseId()) &&
603         //    !MyfacesConfig.getCurrentInstance(
604         //            ctx.getFacesContext().getExternalContext()).isRefreshTransientBuildOnPSSPreserveState())
605         if (fcc.isUsingPSSOnThisView() && !fcc.isRefreshTransientBuildOnPSSPreserveState())
606         {
607             UIViewRoot root = getViewRoot(ctx, parent);
608             FaceletState map = (FaceletState) root.getAttributes().get(FACELET_STATE_INSTANCE);
609             if (map == null)
610             {
611                 value = null;
612             }
613             else
614             {
615                 value = map.getState(uniqueId);
616             }
617         }
618         return value;
619     }
620     
621     public static void saveInitialTagState(FaceletContext ctx, FaceletCompositionContext fcc,
622                                            UIComponent parent, String uniqueId, Object value)
623     {
624         // Only save the value when the view was built the first time, to ensure PSS algorithm 
625         // work correctly. If preserve state is enabled, just ignore it, because this tag will
626         // force full restore over the parent
627         //if (fcc.isUsingPSSOnThisView()) {
628         //    if (!fcc.isRefreshingTransientBuild() && !ctx.getFacesContext().isPostback()
629         //        && !MyfacesConfig.getCurrentInstance(
630         //            ctx.getFacesContext().getExternalContext()).isRefreshTransientBuildOnPSSPreserveState())
631         
632         // If we save the value each time the view is updated, we can use PSS on the dynamic parts,
633         // just calling markInitialState() on the required components, simplifying the algorithm.
634         if (fcc.isUsingPSSOnThisView() && !fcc.isRefreshTransientBuildOnPSSPreserveState())
635         {
636             UIViewRoot root = getViewRoot(ctx, parent);
637             FaceletState map = (FaceletState) root.getAttributes().get(FACELET_STATE_INSTANCE);
638             if (map == null)
639             {
640                 map = new FaceletState();
641                 root.getAttributes().put(FACELET_STATE_INSTANCE, map);
642             }
643             map.putState(uniqueId, value);
644         }
645     }
646 
647 }