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