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 1634158 2014-10-24 23:27:07Z 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     public static UIComponent findChildInChildrenByTagId(UIComponent parent, String id)
211     {
212         if (parent.getChildCount() > 0)
213         {
214             for (int i = 0, childCount = parent.getChildCount(); i < childCount; i ++)
215             {
216                 UIComponent child = parent.getChildren().get(i);
217                 if (id.equals(child.getAttributes().get(MARK_CREATED)))
218                 {
219                     return child;
220                 }
221             }
222         }
223         return null;
224     }
225     
226     public static String findChildInFacetsByTagId(UIComponent parent, String id)
227     {
228         Iterator<Map.Entry<String, UIComponent>> itr = null;
229         if (parent.getFacetCount() > 0)
230         {
231             itr = parent.getFacets().entrySet().iterator();
232             while (itr.hasNext())
233             {
234                 Map.Entry<String, UIComponent> entry = itr.next();
235                 UIComponent facet = entry.getValue();
236                 // check if this is a dynamically generated UIPanel
237                 if (Boolean.TRUE.equals(facet.getAttributes()
238                              .get(FACET_CREATED_UIPANEL_MARKER)))
239                 {
240                     // only check the children and facets of the panel
241                     if (facet.getChildCount() > 0)
242                     {
243                         for (int i = 0, childCount = facet.getChildCount(); i < childCount; i ++)
244                         {
245                             UIComponent child = facet.getChildren().get(i);
246                             if (id.equals(child.getAttributes().get(MARK_CREATED)))
247                             {
248                                 return entry.getKey();
249                             }
250                         }
251                     }
252                     if (facet.getFacetCount() > 0)
253                     {
254                         Iterator<UIComponent> itr2 = facet.getFacets().values().iterator();
255                         while (itr2.hasNext())
256                         {
257                             UIComponent child = itr2.next();
258                             if (id.equals(child.getAttributes().get(MARK_CREATED)))
259                             {
260                                 return entry.getKey();
261                             }
262                         }
263                     }
264                 }
265                 else if (id.equals(facet.getAttributes().get(MARK_CREATED)))
266                 {
267                     return entry.getKey();
268                 }
269             }
270         }
271         return null;
272     }
273 
274     /**
275      * According to JSF 1.2 tag specs, this helper method will use the TagAttribute passed in determining the Locale
276      * intended.
277      * 
278      * @param ctx
279      *            FaceletContext to evaluate from
280      * @param attr
281      *            TagAttribute representing a Locale
282      * @return Locale found
283      * @throws TagAttributeException
284      *             if the Locale cannot be determined
285      */
286     public static Locale getLocale(FaceletContext ctx, TagAttribute attr) throws TagAttributeException
287     {
288         Object obj = attr.getObject(ctx);
289         if (obj instanceof Locale)
290         {
291             return (Locale) obj;
292         }
293         if (obj instanceof String)
294         {
295             String s = (String) obj;
296             if (s.length() == 2)
297             {
298                 return new Locale(s);
299             }
300 
301             if (s.length() == 5)
302             {
303                 return new Locale(s.substring(0, 2), s.substring(3, 5).toUpperCase());
304             }
305 
306             if (s.length() >= 7)
307             {
308                 return new Locale(s.substring(0, 2), s.substring(3, 5).toUpperCase(), s.substring(6, s.length()));
309             }
310 
311             throw new TagAttributeException(attr, "Invalid Locale Specified: " + s);
312         }
313         else
314         {
315             throw new TagAttributeException(attr, "Attribute did not evaluate to a String or Locale: " + obj);
316         }
317     }
318 
319     /**
320      * Tries to walk up the parent to find the UIViewRoot, if not found, then go to FaceletContext's FacesContext for
321      * the view root.
322      * 
323      * @param ctx
324      *            FaceletContext
325      * @param parent
326      *            UIComponent to search from
327      * @return UIViewRoot instance for this evaluation
328      */
329     public static UIViewRoot getViewRoot(FaceletContext ctx, UIComponent parent)
330     {
331         UIComponent c = parent;
332         do
333         {
334             if (c instanceof UIViewRoot)
335             {
336                 return (UIViewRoot) c;
337             }
338             else
339             {
340                 c = c.getParent();
341             }
342         } while (c != null);
343 
344         return ctx.getFacesContext().getViewRoot();
345     }
346     
347     /**
348      * Marks all direct children and Facets with an attribute for deletion.
349      * 
350      * @deprecated use FaceletCompositionContext.markForDeletion
351      * @see #finalizeForDeletion(UIComponent)
352      * @param component
353      *            UIComponent to mark
354      */
355     @Deprecated
356     public static void markForDeletion(UIComponent component)
357     {
358         // flag this component as deleted
359         component.getAttributes().put(MARK_DELETED, Boolean.TRUE);
360 
361         Iterator<UIComponent> iter = component.getFacetsAndChildren();
362         while (iter.hasNext())
363         {
364             UIComponent child = iter.next();
365             if (child.getAttributes().containsKey(MARK_CREATED))
366             {
367                 child.getAttributes().put(MARK_DELETED, Boolean.TRUE);
368             }
369         }
370     }
371 
372     public static void encodeRecursive(FacesContext context, UIComponent toRender) throws IOException, FacesException
373     {
374         if (toRender.isRendered())
375         {
376             toRender.encodeBegin(context);
377             
378             if (toRender.getRendersChildren())
379             {
380                 toRender.encodeChildren(context);
381             }
382             else if (toRender.getChildCount() > 0)
383             {
384                 for (int i = 0, childCount = toRender.getChildCount(); i < childCount; i++)
385                 {
386                     UIComponent child = toRender.getChildren().get(i);
387                     encodeRecursive(context, child);
388                 }
389             }
390             
391             toRender.encodeEnd(context);
392         }
393     }
394 
395     public static void removeTransient(UIComponent component)
396     {
397         if (component.getChildCount() > 0)
398         {
399             for (Iterator<UIComponent> itr = component.getChildren().iterator(); itr.hasNext();)
400             {
401                 UIComponent child = itr.next();
402                 if (child.isTransient())
403                 {
404                     itr.remove();
405                 }
406                 else
407                 {
408                     removeTransient(child);
409                 }
410             }
411         }
412         
413         if (component.getFacetCount() > 0)
414         {
415             Map<String, UIComponent> facets = component.getFacets();
416             for (Iterator<UIComponent> itr = facets.values().iterator(); itr.hasNext();)
417             {
418                 UIComponent facet = itr.next();
419                 if (facet.isTransient())
420                 {
421                     itr.remove();
422                 }
423                 else
424                 {
425                     removeTransient(facet);
426                 }
427             }
428         }
429     }
430 
431     /**
432      * Determine if the passed component is not null and if it's new to the tree. This operation can be used for
433      * determining if attributes should be wired to the component.
434      * 
435      * @deprecated use ComponentHandler.isNew
436      * @param component
437      *            the component you wish to modify
438      * @return true if it's new
439      */
440     @Deprecated
441     public static boolean isNew(UIComponent component)
442     {
443         return component != null && component.getParent() == null;
444     }
445     
446     /**
447      * Create a new UIPanel for the use as a dynamically 
448      * created container for multiple children in a facet.
449      * Duplicate in javax.faces.webapp.UIComponentClassicTagBase.
450      * @param facesContext
451      * @return
452      */
453     private static UIComponent createFacetUIPanel(FaceletContext ctx, UIComponent parent, String facetName)
454     {
455         FacesContext facesContext = ctx.getFacesContext();
456         UIComponent panel = facesContext.getApplication().createComponent(facesContext, UIPanel.COMPONENT_TYPE, null);
457         
458         // The panel created by this method is special. To be restored properly and do not
459         // create duplicate ids or any other unwanted conflicts, it requires an unique id.
460         // This code is usually called when more than one component is added to a facet and
461         // it is necessary to create a shared container.
462         // Use FaceletCompositionContext.generateUniqueComponentId() is not possible, because
463         // <c:if> blocks inside a facet will make component ids unstable. Use UniqueIdVendor
464         // is feasible but also will be affected by <c:if> blocks inside a facet.
465         // The only solution that will generate real unique ids is use the parent id and the
466         // facet name and derive an unique id that cannot be generated by SectionUniqueIdCounter,
467         // doing the same trick as with metadata: use a double __ and add a prefix (f).
468         // Note this id will never be printed into the response, because this is just a container.
469         FaceletCompositionContext mctx = FaceletCompositionContext.getCurrentInstance(ctx);
470         UniqueIdVendor uniqueIdVendor = mctx.getUniqueIdVendorFromStack();
471         if (uniqueIdVendor == null)
472         {
473             uniqueIdVendor = ComponentSupport.getViewRoot(ctx, parent);
474         }
475         if (uniqueIdVendor != null)
476         {
477             // UIViewRoot implements UniqueIdVendor, so there is no need to cast to UIViewRoot
478             // and call createUniqueId(). See ComponentTagHandlerDelegate
479             int index = facetName.indexOf('.');
480             String cleanFacetName = facetName;
481             if (index >= 0)
482             {
483                 cleanFacetName = facetName.replace('.', '_');
484             }
485             panel.setId(uniqueIdVendor.createUniqueId(facesContext, 
486                     mctx.getSharedStringBuilder()
487                       .append(parent.getId())
488                       .append("__f_")
489                       .append(cleanFacetName).toString()));
490         }
491         panel.getAttributes().put(FACET_CREATED_UIPANEL_MARKER, Boolean.TRUE);
492         panel.getAttributes().put(ComponentSupport.COMPONENT_ADDED_BY_HANDLER_MARKER, Boolean.TRUE);
493         return panel;
494     }
495     
496     public static void addFacet(FaceletContext ctx, UIComponent parent, UIComponent c, String facetName)
497     {
498         // facets now can have multiple children and the direct
499         // child of a facet is always an UIPanel (since 2.0)
500         UIComponent facet = parent.getFacets().get(facetName);
501         if (facet == null)
502         {
503             //Just set it directly like  before
504             parent.getFacets().put(facetName, c);
505         }
506         else if (!(facet instanceof UIPanel))
507         {
508             // there is a facet, but it is not an instance of UIPanel
509             UIComponent child = facet;
510             facet = createFacetUIPanel(ctx, parent, facetName);
511             facet.getChildren().add(child);
512             facet.getChildren().add(c);
513             parent.getFacets().put(facetName, facet);
514         }
515         else
516         {
517             // we have a facet, which is an instance of UIPanel at this point
518             // check if it is a facet marked UIPanel
519             if (Boolean.TRUE.equals(facet.getAttributes().get(FACET_CREATED_UIPANEL_MARKER)))
520             {
521                 facet.getChildren().add(c);
522             }
523             else
524             {
525                 // the facet is an instance of UIPanel, but it is not marked,
526                 // so we have to create a new UIPanel and store this one in it
527                 UIComponent oldPanel = facet;
528                 facet = createFacetUIPanel(ctx, parent, facetName);
529                 facet.getChildren().add(oldPanel);
530                 facet.getChildren().add(c);
531                 parent.getFacets().put(facetName, facet);
532             }
533         }
534     }
535     
536     public static void removeFacet(FaceletContext ctx, UIComponent parent, UIComponent c, String facetName)
537     {
538         UIComponent facet = parent.getFacet(facetName);
539         if (Boolean.TRUE.equals(facet.getAttributes().get(FACET_CREATED_UIPANEL_MARKER)))
540         {
541             facet.getChildren().remove(c);
542         }
543         else
544         {
545             parent.getFacets().remove(facetName);
546         }
547     }
548 
549     public static void markComponentToRestoreFully(FacesContext context, UIComponent component)
550     {
551         if (MyfacesConfig.getCurrentInstance(context.getExternalContext()).isRefreshTransientBuildOnPSSPreserveState())
552         {
553             component.getAttributes().put(DefaultFaceletsStateManagementStrategy.COMPONENT_ADDED_AFTER_BUILD_VIEW,
554                                           ComponentState.REMOVE_ADD);
555         }
556         //component.subscribeToEvent(PostAddToViewEvent.class, new RestoreComponentFullyListener());
557         if (FaceletViewDeclarationLanguage.isRefreshTransientBuildOnPSSAuto(context))
558         {
559             FaceletViewDeclarationLanguage.cleanTransientBuildOnRestore(context);
560         }
561     }
562     
563     public static UIComponent findComponentChildOrFacetFrom(FacesContext facesContext, UIComponent parent, String expr)
564     {
565         final char separatorChar = UINamingContainer.getSeparatorChar(facesContext);
566         int separator = expr.indexOf(separatorChar);
567         if (separator == -1)
568         {
569             return ComponentSupport.findComponentChildOrFacetFrom(
570                     parent, expr, null);
571         }
572         else
573         {
574             return ComponentSupport.findComponentChildOrFacetFrom(
575                     parent, expr.substring(0,separator), expr);
576         }
577     }
578     
579     public static UIComponent findComponentChildOrFacetFrom(UIComponent parent, String id, String innerExpr)
580     {
581         if (parent.getFacetCount() > 0)
582         {
583             for (UIComponent facet : parent.getFacets().values())
584             {
585                 if (id.equals(facet.getId()))
586                 {
587                     if (innerExpr == null)
588                     {
589                         return facet;
590                     }
591                     else if (facet instanceof NamingContainer)
592                     {
593                         UIComponent find = facet.findComponent(innerExpr);
594                         if (find != null)
595                         {
596                             return find;
597                         }
598                     }
599                 }
600                 else if (!(facet instanceof NamingContainer))
601                 {
602                     UIComponent find = findComponentChildOrFacetFrom(facet, id, innerExpr);
603                     if (find != null)
604                     {
605                         return find;
606                     }
607                 }
608             }
609         }
610         if (parent.getChildCount() > 0)
611         {
612             for (int i = 0, childCount = parent.getChildCount(); i < childCount; i++)
613             {
614                 UIComponent child = parent.getChildren().get(i);
615                 if (id.equals(child.getId()))
616                 {
617                     if (innerExpr == null)
618                     {
619                         return child;
620                     }
621                     else if (child instanceof NamingContainer)
622                     {
623                         UIComponent find = child.findComponent(innerExpr);
624                         if (find != null)
625                         {
626                             return find;
627                         }
628                     }
629                 }
630                 else if (!(child instanceof NamingContainer))
631                 {
632                     UIComponent find = findComponentChildOrFacetFrom(child, id, innerExpr);
633                     if (find != null)
634                     {
635                         return find;
636                     }
637                 }
638             }
639         }
640         return null;
641     }
642     
643     public static String getFindComponentExpression(FacesContext facesContext, UIComponent component)
644     {
645         char separatorChar = UINamingContainer.getSeparatorChar(facesContext);
646         UIComponent parent = component.getParent();
647         StringBuilder sb = new StringBuilder();
648         sb.append(component.getId());
649         while (parent != null)
650         {
651             if (parent instanceof NamingContainer)
652             {
653                 sb.insert(0, separatorChar);
654                 sb.insert(0, parent.getId());
655             }
656             parent = parent.getParent();
657         }
658         return sb.toString();
659     }
660     
661     public static Object restoreInitialTagState(FaceletContext ctx, FaceletCompositionContext fcc,
662                                                 UIComponent parent, String uniqueId)
663     {
664         Object value = null;
665         //if (fcc.isUsingPSSOnThisView() &&
666         //    PhaseId.RESTORE_VIEW.equals(ctx.getFacesContext().getCurrentPhaseId()) &&
667         //    !MyfacesConfig.getCurrentInstance(
668         //            ctx.getFacesContext().getExternalContext()).isRefreshTransientBuildOnPSSPreserveState())
669         if (fcc.isUsingPSSOnThisView() && !fcc.isRefreshTransientBuildOnPSSPreserveState())
670         {
671             UIViewRoot root = getViewRoot(ctx, parent);
672             FaceletState map = (FaceletState) root.getAttributes().get(FACELET_STATE_INSTANCE);
673             if (map == null)
674             {
675                 value = null;
676             }
677             else
678             {
679                 value = map.getState(uniqueId);
680             }
681         }
682         return value;
683     }
684     
685     public static void saveInitialTagState(FaceletContext ctx, FaceletCompositionContext fcc,
686                                            UIComponent parent, String uniqueId, Object value)
687     {
688         // Only save the value when the view was built the first time, to ensure PSS algorithm 
689         // work correctly. If preserve state is enabled, just ignore it, because this tag will
690         // force full restore over the parent
691         //if (fcc.isUsingPSSOnThisView()) {
692         //    if (!fcc.isRefreshingTransientBuild() && !ctx.getFacesContext().isPostback()
693         //        && !MyfacesConfig.getCurrentInstance(
694         //            ctx.getFacesContext().getExternalContext()).isRefreshTransientBuildOnPSSPreserveState())
695         
696         // If we save the value each time the view is updated, we can use PSS on the dynamic parts,
697         // just calling markInitialState() on the required components, simplifying the algorithm.
698         if (fcc.isUsingPSSOnThisView() && !fcc.isRefreshTransientBuildOnPSSPreserveState())
699         {
700             UIViewRoot root = getViewRoot(ctx, parent);
701             FaceletState map = (FaceletState) root.getAttributes().get(FACELET_STATE_INSTANCE);
702             if (map == null)
703             {
704                 map = new FaceletState();
705                 root.getAttributes().put(FACELET_STATE_INSTANCE, map);
706             }
707             map.putState(uniqueId, value);
708         }
709     }
710 
711 }