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.Collections;
23  import java.util.EnumSet;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  import java.util.logging.Level;
29  import java.util.logging.Logger;
30  
31  import javax.el.ValueExpression;
32  import javax.faces.FacesWrapper;
33  import javax.faces.application.Application;
34  import javax.faces.application.ProjectStage;
35  import javax.faces.component.ActionSource;
36  import javax.faces.component.EditableValueHolder;
37  import javax.faces.component.UIComponent;
38  import javax.faces.component.UIOutput;
39  import javax.faces.component.UniqueIdVendor;
40  import javax.faces.component.ValueHolder;
41  import javax.faces.component.behavior.ClientBehaviorHolder;
42  import javax.faces.component.visit.VisitCallback;
43  import javax.faces.component.visit.VisitContext;
44  import javax.faces.component.visit.VisitHint;
45  import javax.faces.component.visit.VisitResult;
46  import javax.faces.context.FacesContext;
47  import javax.faces.event.PhaseId;
48  import javax.faces.validator.BeanValidator;
49  import javax.faces.validator.Validator;
50  import javax.faces.view.EditableValueHolderAttachedObjectHandler;
51  import javax.faces.view.facelets.ComponentConfig;
52  import javax.faces.view.facelets.ComponentHandler;
53  import javax.faces.view.facelets.FaceletContext;
54  import javax.faces.view.facelets.MetaRuleset;
55  import javax.faces.view.facelets.TagAttribute;
56  import javax.faces.view.facelets.TagException;
57  import javax.faces.view.facelets.TagHandlerDelegate;
58  import javax.faces.view.facelets.ValidatorHandler;
59  
60  import org.apache.myfaces.util.ExternalSpecifications;
61  import org.apache.myfaces.view.facelets.AbstractFaceletContext;
62  import org.apache.myfaces.view.facelets.ComponentState;
63  import org.apache.myfaces.view.facelets.DefaultFaceletsStateManagementStrategy;
64  import org.apache.myfaces.view.facelets.FaceletCompositionContext;
65  import org.apache.myfaces.view.facelets.FaceletDynamicComponentRefreshTransientBuildEvent;
66  import org.apache.myfaces.view.facelets.FaceletViewDeclarationLanguage;
67  import org.apache.myfaces.view.facelets.FaceletViewDeclarationLanguageBase;
68  import org.apache.myfaces.view.facelets.el.CompositeComponentELUtils;
69  import org.apache.myfaces.view.facelets.tag.MetaRulesetImpl;
70  import org.apache.myfaces.view.facelets.tag.jsf.core.AjaxHandler;
71  import org.apache.myfaces.view.facelets.tag.jsf.core.FacetHandler;
72  
73  /**
74   *  
75   * Implementation of the tag logic used in the JSF specification. 
76   * 
77   * See org.apache.myfaces.view.facelets.tag.jsf.ComponentHandler
78   * @author Leonardo Uribe (latest modification by $Author$)
79   * @version $Revision$ $Date$
80   *
81   * @since 2.0
82   */
83  public class ComponentTagHandlerDelegate extends TagHandlerDelegate
84  {
85      private final static Logger log = Logger.getLogger(ComponentTagHandlerDelegate.class.getName());
86      
87      private static final Set<VisitHint> VISIT_HINTS_DYN_REFRESH = Collections.unmodifiableSet( 
88              EnumSet.of(VisitHint.SKIP_ITERATION));
89  
90      private final ComponentHandler _delegate;
91  
92      private final String _componentType;
93  
94      private final TagAttribute _id;
95  
96      private final String _rendererType;
97      
98      private final ComponentBuilderHandler _componentBuilderHandlerDelegate;
99      
100     private final RelocatableResourceHandler _relocatableResourceHandler;
101 
102     @SuppressWarnings("unchecked")
103     public ComponentTagHandlerDelegate(ComponentHandler delegate)
104     {
105         _delegate = delegate;
106         ComponentConfig delegateComponentConfig = delegate.getComponentConfig();
107         _componentType = delegateComponentConfig.getComponentType();
108         _rendererType = delegateComponentConfig.getRendererType();
109         _id = delegate.getTagAttribute("id");      
110         
111         ComponentHandler handler = _delegate;
112         boolean found = false;
113         while(handler != null && !found)
114         {
115             if (handler instanceof ComponentBuilderHandler)
116             {
117                 found = true;
118             }
119             else if (handler instanceof FacesWrapper)
120             {
121                 handler = ((FacesWrapper<? extends ComponentHandler>)handler).getWrapped();
122             }
123             else
124             {
125                 handler = null;
126             }
127         }
128         if (found)
129         {
130             _componentBuilderHandlerDelegate = (ComponentBuilderHandler) handler;
131         }
132         else
133         {
134             _componentBuilderHandlerDelegate = null;
135         }
136         
137         //Check if this component is instance of RelocatableResourceHandler
138         handler = _delegate;
139         found = false;
140         while(handler != null && !found)
141         {
142             if (handler instanceof RelocatableResourceHandler)
143             {
144                 found = true;
145             }
146             else if (handler instanceof FacesWrapper)
147             {
148                 handler = ((FacesWrapper<? extends ComponentHandler>)handler).getWrapped();
149             }
150             else
151             {
152                 handler = null;
153             }
154         }
155         if (found)
156         {
157             _relocatableResourceHandler = (RelocatableResourceHandler) handler;
158         }
159         else
160         {
161             // Check if the component is a relocatable component done overriding the tag handler
162             if (_componentType != null && _rendererType != null &&
163                 (_rendererType.equals("javax.faces.resource.Script") ||
164                  _rendererType.equals("javax.faces.resource.Stylesheet")) &&
165                 _componentType.equals(UIOutput.COMPONENT_TYPE))
166             {
167                 _relocatableResourceHandler = ComponentRelocatableResourceHandler.INSTANCE;
168             }   
169             else
170             {
171                 _relocatableResourceHandler = null;
172             }
173         }
174     }
175 
176     /**
177      * Method handles UIComponent tree creation in accordance with the JSF 1.2 spec.
178      * <ol>
179      * <li>First determines this UIComponent's id by calling {@link #getId(FaceletContext) getId(FaceletContext)}.</li>
180      * <li>Search the parent for an existing UIComponent of the id we just grabbed</li>
181      * <li>If found, {@link FaceletCompositionContext#markForDeletion(UIComponent) mark} its children for deletion.</li>
182      * <li>If <i>not</i> found, call {@link #createComponent(FaceletContext) createComponent}.
183      * <ol>
184      * <li>Only here do we apply TagHandler#setAttributes(FaceletCompositionContext, Object)</li>
185      * <li>Set the UIComponent's id</li>
186      * <li>Set the RendererType of this instance</li>
187      * </ol>
188      * </li>
189      * <li>Now apply the nextHandler, passing the UIComponent we've created/found.</li>
190      * <li>Now add the UIComponent to the passed parent</li>
191      * <li>Lastly, if the UIComponent already existed (found),
192      * then #finalizeForDeletion(FaceletCompositionContext, UIComponent)
193      * for deletion.</li>
194      * </ol>
195      * 
196      * See javax.faces.view.facelets.FaceletHandler#apply(javax.faces.view.facelets.FaceletContext,
197      * javax.faces.component.UIComponent)
198      * 
199      * @throws TagException
200      *             if the UIComponent parent is null
201      */
202     @SuppressWarnings("unchecked")
203     @Override
204     public void apply(FaceletContext ctx, UIComponent parent) throws IOException
205     {
206         // make sure our parent is not null
207         if (parent == null)
208         {
209             throw new TagException(_delegate.getTag(), "Parent UIComponent was null");
210         }
211         
212         FacesContext facesContext = ctx.getFacesContext();
213 
214         // possible facet scoped
215         String facetName = this.getFacetName(ctx, parent);
216 
217         // our id
218         String id = ctx.generateUniqueId(_delegate.getTagId());
219 
220         // Cast to use UniqueIdVendor stuff
221         FaceletCompositionContext mctx = (FaceletCompositionContext) FaceletCompositionContext.getCurrentInstance(ctx);
222                 
223         // grab our component
224         UIComponent c = null;
225         //boolean componentFoundInserted = false;
226 
227         //Used to preserve the original parent. Note when the view is being refreshed, the real parent could be
228         //another component.
229         UIComponent oldParent = parent;
230         
231         if (mctx.isRefreshingSection())
232         {
233             if (_relocatableResourceHandler != null)
234             {
235                 c = _relocatableResourceHandler.findChildByTagId(ctx, parent, id);
236             }
237             else
238             {
239                 if (facetName != null)
240                 {
241                     c = ComponentSupport.findChildInFacetByTagId(parent, id, facetName);
242                 }
243                 else
244                 {
245                     c = ComponentSupport.findChildInChildrenByTagId(parent, id);
246                 }
247             }
248         }
249         boolean componentFound = false;
250         if (c != null)
251         {
252             componentFound = true;
253             // Check if the binding needs dynamic refresh and if that so, invoke the refresh from this location, to
254             // preserve the same context
255             if (_delegate.getBinding() != null &&
256                 c.getAttributes().containsKey(
257                     FaceletDynamicComponentRefreshTransientBuildEvent.DYNAMIC_COMPONENT_BINDING_NEEDS_REFRESH))
258             {
259                 VisitContext visitContext = (VisitContext) mctx.getVisitContextFactory().
260                     getVisitContext(facesContext, null, VISIT_HINTS_DYN_REFRESH);
261                 c.visitTree(visitContext, new PublishFaceletDynamicComponentRefreshTransientBuildCallback());
262             }
263             
264             mctx.incrementUniqueComponentId();
265             
266             // mark all children for cleaning
267             if (log.isLoggable(Level.FINE))
268             {
269                 log.fine(_delegate.getTag() + " Component[" + id + "] Found, marking children for cleanup");
270             }
271 
272             // The call for mctx.markForDeletion(c) is always necessary, because
273             // component resource relocation occur as an effect of PostAddToViewEvent,
274             // so at this point it is unknown if the component was relocated or not.
275             mctx.markForDeletion(c);
276 
277             if (_relocatableResourceHandler != null)
278             {
279                 mctx.markRelocatableResourceForDeletion(c);
280             }
281         }
282         else
283         {
284             c = this.createComponent(ctx);
285             if (log.isLoggable(Level.FINE))
286             {
287                 log.fine(_delegate.getTag() + " Component[" + id + "] Created: " + c.getClass().getName());
288             }
289             
290             _delegate.setAttributes(ctx, c);
291 
292             // mark it owned by a facelet instance
293             c.getAttributes().put(ComponentSupport.MARK_CREATED, id);
294 
295             if (facesContext.isProjectStage(ProjectStage.Development))
296             {
297                 c.getAttributes().put(UIComponent.VIEW_LOCATION_KEY,
298                         _delegate.getTag().getLocation());
299             }
300 
301             // assign our unique id
302             if (this._id != null)
303             {
304                 mctx.incrementUniqueComponentId();
305                 c.setId(this._id.getValue(ctx));
306             }
307             else
308             {
309                 String componentId = mctx.generateUniqueComponentId();
310                 UniqueIdVendor uniqueIdVendor = mctx.getUniqueIdVendorFromStack();
311                 if (uniqueIdVendor == null)
312                 {
313                     uniqueIdVendor = facesContext.getViewRoot();
314                     
315                     if (uniqueIdVendor == null)
316                     {
317                         // facesContext.getViewRoot() returns null here if we are in
318                         // phase restore view, so we have to try to get the view root
319                         // via the method in ComponentSupport and our parent
320                         uniqueIdVendor = ComponentSupport.getViewRoot(ctx, parent);
321                     }
322                 }
323                 if (uniqueIdVendor != null)
324                 {
325                     // UIViewRoot implements UniqueIdVendor, so there is no need to cast to UIViewRoot
326                     // and call createUniqueId()
327                     String uid = uniqueIdVendor.createUniqueId(facesContext, componentId);
328                     c.setId(uid);
329                 }
330             }
331 
332             if (this._rendererType != null)
333             {
334                 c.setRendererType(this._rendererType);
335             }
336 
337             // hook method
338             _delegate.onComponentCreated(ctx, c, parent);
339             
340             if (_relocatableResourceHandler != null && 
341                 _relocatableResourceHandler instanceof ComponentRelocatableResourceHandler)
342             {
343                 UIComponent parentCompositeComponent
344                         = mctx.getCompositeComponentFromStack();
345                 if (parentCompositeComponent != null)
346                 {
347                     c.getAttributes().put(CompositeComponentELUtils.LOCATION_KEY,
348                             parentCompositeComponent.getAttributes().get(CompositeComponentELUtils.LOCATION_KEY));
349                 }
350             }
351             
352             if (mctx.isRefreshingTransientBuild() && _relocatableResourceHandler != null)
353             {
354                 mctx.markRelocatableResourceForDeletion(c);
355             }
356         }
357         c.pushComponentToEL(facesContext, c);
358 
359         if (c instanceof UniqueIdVendor)
360         {
361             mctx.pushUniqueIdVendorToStack((UniqueIdVendor)c);
362         }
363         
364         if (mctx.isDynamicComponentTopLevel())
365         {
366             mctx.setDynamicComponentTopLevel(false);
367             _delegate.applyNextHandler(ctx, c);
368             mctx.setDynamicComponentTopLevel(true);
369         }
370         else
371         {
372             // first allow c to get populated
373             _delegate.applyNextHandler(ctx, c);
374         }
375         
376         boolean oldProcessingEvents = facesContext.isProcessingEvents();
377         // finish cleaning up orphaned children
378         if (componentFound && !mctx.isDynamicComponentTopLevel())
379         {
380             mctx.finalizeForDeletion(c);
381 
382             //if (!componentFoundInserted)
383             //{
384                 if (mctx.isRefreshingSection())
385                 {
386                     facesContext.setProcessingEvents(false);
387                     if (_relocatableResourceHandler != null &&
388                         parent != null && !parent.equals(c.getParent()))
389                     {
390                         // Replace parent with the relocated parent.
391                         parent = c.getParent();
392                         // Since we changed the parent, the facetName becomes invalid, because it points
393                         // to the component before relocation. We need to find the right facetName (if any) so we can
394                         // refresh the component properly.
395                         UIComponent c1 = ComponentSupport.findChildInChildrenByTagId(parent, id);
396                         if (c1 == null)
397                         {
398                             facetName = ComponentSupport.findChildInFacetsByTagId(parent, id);
399                         }
400                         else
401                         {
402                             facetName = null;
403                         }
404                     }
405                     ComponentSupport.setCachedFacesContext(c, facesContext);
406                 }
407                 if (facetName == null)
408                 {
409                     parent.getChildren().remove(c);
410                 }
411                 else
412                 {
413                     ComponentSupport.removeFacet(ctx, parent, c, facetName);
414                 }
415                 if (mctx.isRefreshingSection())
416                 {
417                     ComponentSupport.setCachedFacesContext(c, null);
418                     facesContext.setProcessingEvents(oldProcessingEvents);
419                 }
420             //}
421         }
422 
423 
424         if (!componentFound)
425         {
426             if (c instanceof ClientBehaviorHolder && !UIComponent.isCompositeComponent(c))
427             {
428                 Iterator<AjaxHandler> it = ((AbstractFaceletContext) ctx).getAjaxHandlers();
429                 if (it != null)
430                 {
431                     while(it.hasNext())
432                     {
433                         it.next().applyAttachedObject(facesContext, c);
434                     }
435                 }
436             }
437             
438             if (c instanceof EditableValueHolder)
439             {
440                 // add default validators here, because this feature 
441                 // is only available in facelets (see MYFACES-2362 for details)
442                 addEnclosingAndDefaultValidators(ctx, mctx, facesContext, (EditableValueHolder) c);
443             }
444         }
445         
446         _delegate.onComponentPopulated(ctx, c, oldParent);
447 
448         if (!mctx.isDynamicComponentTopLevel() || !componentFound)
449         {
450             if (componentFound && mctx.isRefreshingSection())
451             {
452                 facesContext.setProcessingEvents(false);
453                 ComponentSupport.setCachedFacesContext(c, facesContext);
454             }
455             if (facetName == null)
456             {
457                 parent.getChildren().add(c);
458             }
459             else
460             {
461                 ComponentSupport.addFacet(ctx, parent, c, facetName);
462             }
463             if (componentFound && mctx.isRefreshingSection())
464             {
465                 ComponentSupport.setCachedFacesContext(c, null);
466                 facesContext.setProcessingEvents(oldProcessingEvents);
467             }
468         }
469 
470         if (c instanceof UniqueIdVendor)
471         {
472             mctx.popUniqueIdVendorToStack();
473         }
474 
475         c.popComponentFromEL(facesContext);
476         
477         if (mctx.isMarkInitialState())
478         {
479             //Call it only if we are using partial state saving
480             c.markInitialState();
481         }
482     }
483     
484     /**
485      * Return the Facet name we are scoped in, otherwise null
486      * 
487      * @param ctx
488      * @return
489      */
490     protected final String getFacetName(FaceletContext ctx, UIComponent parent)
491     {
492         return (String) parent.getAttributes().get(FacetHandler.KEY);
493     }
494 
495     /**
496      * If the binding attribute was specified, use that in conjuction with our componentType String variable to call
497      * createComponent on the Application, otherwise just pass the componentType String. <p> If the binding was used,
498      * then set the ValueExpression "binding" on the created UIComponent.</p>
499      * 
500      * See Application#createComponent(javax.faces.el.ValueBinding, javax.faces.context.FacesContext, java.lang.String)
501      * See Application#createComponent(java.lang.String)
502      * @param ctx
503      *            FaceletContext to use in creating a component
504      * @return
505      */
506     protected UIComponent createComponent(FaceletContext ctx)
507     {
508         if (_componentBuilderHandlerDelegate != null)
509         {
510             // the call to Application.createComponent(FacesContext, Resource)
511             // is delegated because we don't have here the required Resource instance
512             return _componentBuilderHandlerDelegate.createComponent(ctx);
513         }
514         UIComponent c = null;
515         FacesContext faces = ctx.getFacesContext();
516         Application app = faces.getApplication();
517         if (_delegate.getBinding() != null)
518         {
519             ValueExpression ve = _delegate.getBinding().getValueExpression(ctx, Object.class);
520             if (PhaseId.RESTORE_VIEW.equals(faces.getCurrentPhaseId()))
521             {
522                 if (!ve.isReadOnly(faces.getELContext()))
523                 {
524                     try
525                     {
526                         // force reset it is an easy and cheap way to allow "binding" attribute to work on 
527                         // view scope beans or flow scope beans (using a transient variable)
528                         ve.setValue(faces.getELContext(), null);
529                     }
530                     catch (Exception e)
531                     {
532                         // ignore
533                     }
534                 }
535             }
536             if (this._rendererType == null)
537             {
538                 c = app.createComponent(ve, faces, this._componentType);
539             }
540             else
541             {
542                 c = app.createComponent(ve, faces, this._componentType, this._rendererType);
543             }
544             if (c != null)
545             {
546                 c.setValueExpression("binding", ve);
547                 
548                 if (!ve.isReadOnly(faces.getELContext()))
549                 {
550                     ComponentSupport.getViewRoot(ctx, c).getAttributes().put("oam.CALL_PRE_DISPOSE_VIEW", Boolean.TRUE);
551                     c.subscribeToEvent(PreDisposeViewEvent.class, new ClearBindingValueExpressionListener());
552                 }
553                 
554                 if (c.getChildCount() > 0 || c.getFacetCount() > 0)
555                 {
556                     // In this case, this component is used to hold a subtree that is generated
557                     // dynamically. In this case, the best is mark this component to be restored
558                     // fully, because this ensures the state is correctly preserved. Note this
559                     // is only necessary when the component has additional children or facets,
560                     // because those components requires an unique id provided by createUniqueId(),
561                     // and this ensures stability of the generated ids.
562                     c.getAttributes().put(DefaultFaceletsStateManagementStrategy.COMPONENT_ADDED_AFTER_BUILD_VIEW,
563                                           ComponentState.REMOVE_ADD);
564                     
565                     if (FaceletViewDeclarationLanguageBase.isDynamicComponentNeedsRefresh(ctx.getFacesContext()))
566                     {
567                         FaceletViewDeclarationLanguageBase.resetDynamicComponentNeedsRefreshFlag(
568                                 ctx.getFacesContext());
569                         FaceletCompositionContext mctx = FaceletCompositionContext.getCurrentInstance(ctx);
570                         if (mctx.isUsingPSSOnThisView())
571                         {
572                             FaceletViewDeclarationLanguage.cleanTransientBuildOnRestore(faces);
573                         }
574                         else
575                         {
576                             FaceletViewDeclarationLanguageBase.activateDynamicComponentRefreshTransientBuild(faces);
577                         }
578                         //
579                         // Mark top binding component to be dynamically refreshed. In that way, facelets algorithm
580                         // will be able to decide if the component children requires to be refreshed dynamically 
581                         // or not.
582                         c.getAttributes().put(
583                                 FaceletDynamicComponentRefreshTransientBuildEvent.
584                                     DYNAMIC_COMPONENT_BINDING_NEEDS_REFRESH,
585                                 Boolean.TRUE);
586                     }
587                 }
588             }
589         }
590         else
591         {
592             // According to the, spec call the second alternative with null rendererType gives
593             // the same result, but without the unnecesary call for FacesContext.getCurrentInstance().
594             // Saves 1 call per component without rendererType (f:viewParam, h:column, f:selectItem, ...)
595             // and it does not have any side effects (the spec javadoc mentions in a explicit way
596             // that rendererType can be null!).
597             /*
598             if (this._rendererType == null)
599             {
600                 c = app.createComponent(this._componentType);
601             }
602             else
603             {*/
604                 c = app.createComponent(faces, this._componentType, this._rendererType);
605             //}
606         }
607         return c;
608     }
609 
610     /**
611      * If the id TagAttribute was specified, get it's value, otherwise generate a unique id from our tagId.
612      * 
613      * See TagAttribute#getValue(FaceletContext)
614      * @param ctx
615      *            FaceletContext to use
616      * @return what should be a unique Id
617      */
618     protected String getId(FaceletContext ctx)
619     {
620         if (this._id != null)
621         {
622             return this._id.getValue(ctx);
623         }
624         return ctx.generateUniqueId(_delegate.getTagId());
625     }
626 
627     @Override
628     public MetaRuleset createMetaRuleset(Class type)
629     {
630         MetaRuleset m = new MetaRulesetImpl(_delegate.getTag(), type);
631         // ignore standard component attributes
632         m.ignore("binding").ignore("id");
633 
634         // add auto wiring for attributes
635         m.addRule(ComponentRule.INSTANCE);
636         
637         // add special rule for passthrough attributes
638         m.addRule(PassthroughRuleImpl.INSTANCE);
639 
640         // if it's an ActionSource
641         if (ActionSource.class.isAssignableFrom(type))
642         {
643             m.addRule(ActionSourceRule.INSTANCE);
644         }
645 
646         // if it's a ValueHolder
647         if (ValueHolder.class.isAssignableFrom(type))
648         {
649             m.addRule(ValueHolderRule.INSTANCE);
650 
651             // if it's an EditableValueHolder
652             if (EditableValueHolder.class.isAssignableFrom(type))
653             {
654                 m.ignore("submittedValue");
655                 m.ignore("valid");
656                 m.addRule(EditableValueHolderRule.INSTANCE);
657             }
658         }
659         
660         // allow components to specify the "class" attribute 
661         m.alias("class", "styleClass");
662         
663         return m;
664     }
665     
666     /**
667      * Add the default Validators to the component.
668      * Also adds all validators specified by enclosing <f:validateBean> tags
669      * (e.g. the BeanValidator if it is not a default validator).
670      *
671      * @param context The FacesContext.
672      * @param mctx the AbstractFaceletContext
673      * @param component The EditableValueHolder to which the validators should be added
674      */
675     private void addEnclosingAndDefaultValidators(FaceletContext ctx, 
676                                       FaceletCompositionContext mctx, 
677                                       FacesContext context, 
678                                       EditableValueHolder component)
679     {
680         // add all enclosing validators, because they have precedence over default validators.
681         Iterator<Map.Entry<String, EditableValueHolderAttachedObjectHandler>> enclosingValidatorIds =
682             mctx.getEnclosingValidatorIdsAndHandlers();
683         if (enclosingValidatorIds != null)
684         {
685             while (enclosingValidatorIds.hasNext())
686             {
687                 Map.Entry<String, EditableValueHolderAttachedObjectHandler> entry = enclosingValidatorIds.next();
688                 addEnclosingValidator(context, component, entry.getKey(), entry.getValue());
689             }
690         }
691         // add all defaultValidators
692         Map<String, String> defaultValidators = context.getApplication().getDefaultValidatorInfo();
693         if (defaultValidators != null && !defaultValidators.isEmpty())
694         {
695             for (Map.Entry<String, String> entry : defaultValidators.entrySet())
696             {
697                 if (!mctx.containsEnclosingValidatorId(entry.getKey()))
698                 {
699                     addDefaultValidator(ctx, mctx, context, component, entry.getKey(), entry.getValue());
700                 }
701             }
702         }
703     }
704 
705     private void addDefaultValidator(FaceletContext ctx, FaceletCompositionContext mctx, FacesContext context, 
706             EditableValueHolder component, String validatorId, String validatorClassName)
707     {
708         Validator enclosingValidator = null;
709         
710         if (validatorClassName == null)
711         {
712             // we have no class name for validators of enclosing <f:validateBean> tags
713             // --> we have to create it to get the class name
714             // note that normally we can use this instance later anyway!
715             enclosingValidator = context.getApplication().createValidator(validatorId);
716             validatorClassName = enclosingValidator.getClass().getName();
717         }
718         
719         // check if the validator is already registered for the given component
720         // this happens if <f:validateBean /> is nested inside the component on the view
721         Validator validator = null;
722         for (Validator v : component.getValidators())
723         {
724             if (v.getClass().getName().equals(validatorClassName))
725             {
726                 // found
727                 validator = v;
728                 break;
729             }
730         }
731         
732         if (validator == null)
733         {
734             if (shouldAddDefaultValidator(ctx, mctx, component, validatorId))
735             {
736                 if (enclosingValidator != null)
737                 {
738                     // we can use the instance from before
739                     validator = enclosingValidator;
740                 }
741                 else
742                 {
743                     // create it
744                     validator = context.getApplication().createValidator(validatorId);
745                 }
746                 // add the validator to the component
747                 component.addValidator(validator);
748             }
749             else
750             {
751                 // we should not add the validator
752                 return;
753             }
754         }
755         
756         // special things to configure for a BeanValidator
757         if (validator instanceof BeanValidator)
758         {
759             BeanValidator beanValidator = (BeanValidator) validator;
760             
761             // check the validationGroups
762             String validationGroups =  beanValidator.getValidationGroups();
763             if (validationGroups == null 
764                     || validationGroups.matches(BeanValidator.EMPTY_VALIDATION_GROUPS_PATTERN))
765             {
766                 // no validationGroups available
767                 // --> get the validationGroups from the stack
768                 //String stackGroup = mctx.getFirstValidationGroupFromStack();
769                 //if (stackGroup != null)
770                 //{
771                 //    validationGroups = stackGroup;
772                 //}
773                 //else
774                 //{
775                     // no validationGroups on the stack
776                     // --> set the default validationGroup
777                     validationGroups = javax.validation.groups.Default.class.getName();
778                 //}
779                 beanValidator.setValidationGroups(validationGroups);
780             }
781         }
782     }
783 
784     /**
785      * Determine if the validator with the given validatorId should be added.
786      *
787      * @param validatorId The validatorId.
788      * @param facesContext The FacesContext.
789      * @param mctx the AbstractFaceletContext
790      * @param component The EditableValueHolder to which the validator should be added.
791      * @return true if the Validator should be added, false otherwise.
792      */
793     @SuppressWarnings("unchecked")
794     private boolean shouldAddDefaultValidator(FaceletContext ctx, FaceletCompositionContext mctx,
795                                               EditableValueHolder component, 
796                                               String validatorId)
797     {
798         // check if the validatorId is on the exclusion list on the component
799         List<String> exclusionList 
800                 = (List<String>) ((UIComponent) component).getAttributes()
801                         .get(ValidatorTagHandlerDelegate.VALIDATOR_ID_EXCLUSION_LIST_KEY);
802         if (exclusionList != null)
803         {
804             for (String excludedId : exclusionList)
805             {
806                 if (excludedId.equals(validatorId))
807                 {
808                     return false;
809                 }
810             }
811         }
812         
813         // check if the validatorId is on the exclusion list on the stack
814         /*
815         Iterator<String> it = mctx.getExcludedValidatorIds();
816         if (it != null)
817         {            
818             while (it.hasNext())
819             {
820                 String excludedId = it.next();
821                 if (excludedId.equals(validatorId))
822                 {
823                     return false;
824                 }
825             }
826         }*/
827         Iterator<Map.Entry<String, EditableValueHolderAttachedObjectHandler>> enclosingValidatorIds =
828             mctx.getEnclosingValidatorIdsAndHandlers();
829         if (enclosingValidatorIds != null)
830         {
831             while (enclosingValidatorIds.hasNext())
832             {
833                 Map.Entry<String, EditableValueHolderAttachedObjectHandler> entry = enclosingValidatorIds.next();
834                 boolean validatorIdAvailable = entry.getKey() != null && !"".equals(entry.getKey());
835                 if (validatorIdAvailable && entry.getKey().equals(validatorId))
836                 {
837                     if (((ValidatorHandler)((FacesWrapper<ValidatorHandler>)entry.getValue()).getWrapped())
838                             .isDisabled(ctx))
839                     {
840                         return false;
841                     }
842                 }
843             }
844         }
845         
846         // Some extra rules are required for Bean Validation.
847         if (validatorId.equals(BeanValidator.VALIDATOR_ID))
848         {
849             if (!ExternalSpecifications.isBeanValidationAvailable())
850             {
851                 // the BeanValidator was added as a default-validator, but
852                 // bean validation is not available on the classpath.
853                 // --> log a warning about this scenario.
854                 log.log(Level.WARNING, "Bean validation is not available on the " +
855                         "classpath, thus the BeanValidator will not be added for " +
856                         "the component " + component);
857                 return false;
858             }
859         }
860 
861         // By default, all default validators should be added
862         return true;
863     }
864 
865     private void addEnclosingValidator(FacesContext context, 
866             EditableValueHolder component, String validatorId, 
867             EditableValueHolderAttachedObjectHandler attachedObjectHandler)
868     {
869         if (shouldAddEnclosingValidator(component, validatorId))
870         {
871             if (attachedObjectHandler != null)
872             {
873                 attachedObjectHandler.applyAttachedObject(context, (UIComponent) component);
874             }
875             else
876             {
877                 Validator validator = null;
878                 // create it
879                 validator = context.getApplication().createValidator(validatorId);
880 
881                 // special things to configure for a BeanValidator
882                 if (validator instanceof BeanValidator)
883                 {
884                     BeanValidator beanValidator = (BeanValidator) validator;
885                     
886                     // check the validationGroups
887                     String validationGroups =  beanValidator.getValidationGroups();
888                     if (validationGroups == null 
889                             || validationGroups.matches(BeanValidator.EMPTY_VALIDATION_GROUPS_PATTERN))
890                     {
891                         // no validationGroups available
892                         // --> get the validationGroups from the stack
893                         //String stackGroup = mctx.getFirstValidationGroupFromStack();
894                         //if (stackGroup != null)
895                         //{
896                         //    validationGroups = stackGroup;
897                         //}
898                         //else
899                         //{
900                             // no validationGroups on the stack
901                             // --> set the default validationGroup
902                             validationGroups = javax.validation.groups.Default.class.getName();
903                         //}
904                         beanValidator.setValidationGroups(validationGroups);
905                     }
906                 }
907                 
908                 // add the validator to the component
909                 component.addValidator(validator);
910             }
911         }
912     }
913 
914     /**
915      * Determine if the validator with the given validatorId should be added.
916      * 
917      * The difference here with shouldAddEnclosingValidator is the inner one has
918      * precedence over the outer one, so a disable="true" over the same outer 
919      * validator, the inner one should ignore this condition. 
920      * 
921      * @param mctx
922      * @param facesContext
923      * @param component
924      * @param validatorId
925      * @return
926      */
927     @SuppressWarnings("unchecked")
928     private boolean shouldAddEnclosingValidator(
929             EditableValueHolder component, 
930             String validatorId)
931     {
932         // check if the validatorId is on the exclusion list on the component
933         List<String> exclusionList = (List<String>) ((UIComponent) component)
934                 .getAttributes()
935                 .get(ValidatorTagHandlerDelegate.VALIDATOR_ID_EXCLUSION_LIST_KEY);
936         if (exclusionList != null)
937         {
938             for (String excludedId : exclusionList)
939             {
940                 if (excludedId.equals(validatorId))
941                 {
942                     return false;
943                 }
944             }
945         }
946 
947         // Some extra rules are required for Bean Validation.
948         if (validatorId.equals(BeanValidator.VALIDATOR_ID) &&
949             !ExternalSpecifications.isBeanValidationAvailable())
950         {
951             // the BeanValidator was added as a default-validator, but
952             // bean validation is not available on the classpath.
953             // --> log a warning about this scenario.
954             log.log(Level.WARNING,
955                     "Bean validation is not available on the "
956                             + "classpath, thus the BeanValidator will not be added for "
957                             + "the component " + component);
958             return false;
959         }
960 
961         // By default, all default validators should be added
962         return true;
963     }
964     
965     private static class PublishFaceletDynamicComponentRefreshTransientBuildCallback implements VisitCallback
966     {
967         public VisitResult visit(VisitContext context, UIComponent target)
968         {
969             context.getFacesContext().getApplication().publishEvent(
970                     context.getFacesContext(), FaceletDynamicComponentRefreshTransientBuildEvent.class, 
971                     target.getClass(), target);
972             return VisitResult.ACCEPT;
973         }
974     }
975 }