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