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: 1634158 $ $Date: 2014-10-24 23:27:07 +0000 (Fri, 24 Oct 2014) $
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                         // Since we changed the parent, the facetName becomes invalid, because it points
329                         // to the component before relocation. We need to find the right facetName (if any) so we can
330                         // refresh the component properly.
331                         UIComponent c1 = ComponentSupport.findChildInChildrenByTagId(parent, id);
332                         if (c1 == null)
333                         {
334                             facetName = ComponentSupport.findChildInFacetsByTagId(parent, id);
335                         }
336                         else
337                         {
338                             facetName = null;
339                         }
340                     }
341                 }
342                 if (facetName == null)
343                 {
344                     parent.getChildren().remove(c);
345                 }
346                 else
347                 {
348                     ComponentSupport.removeFacet(ctx, parent, c, facetName);
349                 }
350                 if (mctx.isRefreshingSection())
351                 {
352                     facesContext.setProcessingEvents(oldProcessingEvents);
353                 }
354             //}
355         }
356 
357 
358         if (!componentFound)
359         {
360             if (c instanceof ClientBehaviorHolder && !UIComponent.isCompositeComponent(c))
361             {
362                 Iterator<AjaxHandler> it = ((AbstractFaceletContext) ctx).getAjaxHandlers();
363                 if (it != null)
364                 {
365                     while(it.hasNext())
366                     {
367                         it.next().applyAttachedObject(facesContext, c);
368                     }
369                 }
370             }
371             
372             if (c instanceof EditableValueHolder)
373             {
374                 // add default validators here, because this feature 
375                 // is only available in facelets (see MYFACES-2362 for details)
376                 addEnclosingAndDefaultValidators(ctx, mctx, facesContext, (EditableValueHolder) c);
377             }
378         }
379         
380         _delegate.onComponentPopulated(ctx, c, oldParent);
381 
382         if (componentFound && mctx.isRefreshingSection())
383         {
384             facesContext.setProcessingEvents(false);
385         }
386         if (facetName == null)
387         {
388             parent.getChildren().add(c);
389         }
390         else
391         {
392             ComponentSupport.addFacet(ctx, parent, c, facetName);
393         }
394         if (componentFound && mctx.isRefreshingSection())
395         {
396             facesContext.setProcessingEvents(oldProcessingEvents);
397         }
398 
399         if (c instanceof UniqueIdVendor)
400         {
401             mctx.popUniqueIdVendorToStack();
402         }
403 
404         c.popComponentFromEL(facesContext);
405         
406         if (mctx.isMarkInitialState())
407         {
408             //Call it only if we are using partial state saving
409             c.markInitialState();
410         }
411     }
412     
413     /**
414      * Return the Facet name we are scoped in, otherwise null
415      * 
416      * @param ctx
417      * @return
418      */
419     protected final String getFacetName(FaceletContext ctx, UIComponent parent)
420     {
421         return (String) parent.getAttributes().get(FacetHandler.KEY);
422     }
423 
424     /**
425      * If the binding attribute was specified, use that in conjuction with our componentType String variable to call
426      * createComponent on the Application, otherwise just pass the componentType String. <p /> If the binding was used,
427      * then set the ValueExpression "binding" on the created UIComponent.
428      * 
429      * @see Application#createComponent(javax.faces.el.ValueBinding, javax.faces.context.FacesContext, java.lang.String)
430      * @see Application#createComponent(java.lang.String)
431      * @param ctx
432      *            FaceletContext to use in creating a component
433      * @return
434      */
435     protected UIComponent createComponent(FaceletContext ctx)
436     {
437         if (_componentBuilderHandlerDelegate != null)
438         {
439             // the call to Application.createComponent(FacesContext, Resource)
440             // is delegated because we don't have here the required Resource instance
441             return _componentBuilderHandlerDelegate.createComponent(ctx);
442         }
443         UIComponent c = null;
444         FacesContext faces = ctx.getFacesContext();
445         Application app = faces.getApplication();
446         if (_delegate.getBinding() != null)
447         {
448             ValueExpression ve = _delegate.getBinding().getValueExpression(ctx, Object.class);
449             if (this._rendererType == null)
450             {
451                 c = app.createComponent(ve, faces, this._componentType);
452             }
453             else
454             {
455                 c = app.createComponent(ve, faces, this._componentType, this._rendererType);
456             }
457             if (c != null)
458             {
459                 c.setValueExpression("binding", ve);
460                 
461                 if (!ve.isReadOnly(faces.getELContext()))
462                 {
463                     ComponentSupport.getViewRoot(ctx, c).getAttributes().put("oam.CALL_PRE_DISPOSE_VIEW", Boolean.TRUE);
464                     c.subscribeToEvent(PreDisposeViewEvent.class, new ClearBindingValueExpressionListener());
465                 }
466                 
467                 if (c.getChildCount() > 0 || c.getFacetCount() > 0)
468                 {
469                     // In this case, this component is used to hold a subtree that is generated
470                     // dynamically. In this case, the best is mark this component to be restored
471                     // fully, because this ensures the state is correctly preserved. Note this
472                     // is only necessary when the component has additional children or facets,
473                     // because those components requires an unique id provided by createUniqueId(),
474                     // and this ensures stability of the generated ids.
475                     c.getAttributes().put(DefaultFaceletsStateManagementStrategy.COMPONENT_ADDED_AFTER_BUILD_VIEW,
476                                           ComponentState.REMOVE_ADD);
477                 }
478             }
479         }
480         else
481         {
482             // According to the, spec call the second alternative with null rendererType gives
483             // the same result, but without the unnecesary call for FacesContext.getCurrentInstance().
484             // Saves 1 call per component without rendererType (f:viewParam, h:column, f:selectItem, ...)
485             // and it does not have any side effects (the spec javadoc mentions in a explicit way
486             // that rendererType can be null!).
487             /*
488             if (this._rendererType == null)
489             {
490                 c = app.createComponent(this._componentType);
491             }
492             else
493             {*/
494                 c = app.createComponent(faces, this._componentType, this._rendererType);
495             //}
496         }
497         return c;
498     }
499 
500     /**
501      * If the id TagAttribute was specified, get it's value, otherwise generate a unique id from our tagId.
502      * 
503      * @see TagAttribute#getValue(FaceletContext)
504      * @param ctx
505      *            FaceletContext to use
506      * @return what should be a unique Id
507      */
508     protected String getId(FaceletContext ctx)
509     {
510         if (this._id != null)
511         {
512             return this._id.getValue(ctx);
513         }
514         return ctx.generateUniqueId(_delegate.getTagId());
515     }
516 
517     @Override
518     public MetaRuleset createMetaRuleset(Class type)
519     {
520         MetaRuleset m = new MetaRulesetImpl(_delegate.getTag(), type);
521         // ignore standard component attributes
522         m.ignore("binding").ignore("id");
523 
524         // add auto wiring for attributes
525         m.addRule(ComponentRule.Instance);
526 
527         // if it's an ActionSource
528         if (ActionSource.class.isAssignableFrom(type))
529         {
530             m.addRule(ActionSourceRule.Instance);
531         }
532 
533         // if it's a ValueHolder
534         if (ValueHolder.class.isAssignableFrom(type))
535         {
536             m.addRule(ValueHolderRule.Instance);
537 
538             // if it's an EditableValueHolder
539             if (EditableValueHolder.class.isAssignableFrom(type))
540             {
541                 m.ignore("submittedValue");
542                 m.ignore("valid");
543                 m.addRule(EditableValueHolderRule.Instance);
544             }
545         }
546         
547         return m;
548     }
549     
550     /**
551      * Add the default Validators to the component.
552      * Also adds all validators specified by enclosing <f:validateBean> tags
553      * (e.g. the BeanValidator if it is not a default validator).
554      *
555      * @param context The FacesContext.
556      * @param mctx the AbstractFaceletContext
557      * @param component The EditableValueHolder to which the validators should be added
558      */
559     private void addEnclosingAndDefaultValidators(FaceletContext ctx, 
560                                       FaceletCompositionContext mctx, 
561                                       FacesContext context, 
562                                       EditableValueHolder component)
563     {
564         // add all enclosing validators, because they have precedence over default validators.
565         Iterator<Map.Entry<String, EditableValueHolderAttachedObjectHandler>> enclosingValidatorIds =
566             mctx.getEnclosingValidatorIdsAndHandlers();
567         if (enclosingValidatorIds != null)
568         {
569             while (enclosingValidatorIds.hasNext())
570             {
571                 Map.Entry<String, EditableValueHolderAttachedObjectHandler> entry = enclosingValidatorIds.next();
572                 addEnclosingValidator(ctx, mctx, context, component, entry.getKey(), entry.getValue());
573             }
574         }
575         // add all defaultValidators
576         Map<String, String> defaultValidators = context.getApplication().getDefaultValidatorInfo();
577         if (defaultValidators != null && defaultValidators.size() != 0)
578         {
579             for (Map.Entry<String, String> entry : defaultValidators.entrySet())
580             {
581                 if (!mctx.containsEnclosingValidatorId(entry.getKey()))
582                 {
583                     addDefaultValidator(ctx, mctx, context, component, entry.getKey(), entry.getValue());
584                 }
585             }
586         }
587     }
588 
589     private void addDefaultValidator(FaceletContext ctx, FaceletCompositionContext mctx, FacesContext context, 
590             EditableValueHolder component, String validatorId, String validatorClassName)
591     {
592         Validator enclosingValidator = null;
593         
594         if (validatorClassName == null)
595         {
596             // we have no class name for validators of enclosing <f:validateBean> tags
597             // --> we have to create it to get the class name
598             // note that normally we can use this instance later anyway!
599             enclosingValidator = context.getApplication().createValidator(validatorId);
600             validatorClassName = enclosingValidator.getClass().getName();
601         }
602         
603         // check if the validator is already registered for the given component
604         // this happens if <f:validateBean /> is nested inside the component on the view
605         Validator validator = null;
606         for (Validator v : component.getValidators())
607         {
608             if (v.getClass().getName().equals(validatorClassName))
609             {
610                 // found
611                 validator = v;
612                 break;
613             }
614         }
615         
616         if (validator == null)
617         {
618             if (shouldAddDefaultValidator(ctx, mctx, context, component, validatorId))
619             {
620                 if (enclosingValidator != null)
621                 {
622                     // we can use the instance from before
623                     validator = enclosingValidator;
624                 }
625                 else
626                 {
627                     // create it
628                     validator = context.getApplication().createValidator(validatorId);
629                 }
630                 // add the validator to the component
631                 component.addValidator(validator);
632             }
633             else
634             {
635                 // we should not add the validator
636                 return;
637             }
638         }
639         
640         // special things to configure for a BeanValidator
641         if (validator instanceof BeanValidator)
642         {
643             BeanValidator beanValidator = (BeanValidator) validator;
644             
645             // check the validationGroups
646             String validationGroups =  beanValidator.getValidationGroups();
647             if (validationGroups == null 
648                     || validationGroups.matches(BeanValidator.EMPTY_VALIDATION_GROUPS_PATTERN))
649             {
650                 // no validationGroups available
651                 // --> get the validationGroups from the stack
652                 //String stackGroup = mctx.getFirstValidationGroupFromStack();
653                 //if (stackGroup != null)
654                 //{
655                 //    validationGroups = stackGroup;
656                 //}
657                 //else
658                 //{
659                     // no validationGroups on the stack
660                     // --> set the default validationGroup
661                     validationGroups = javax.validation.groups.Default.class.getName();
662                 //}
663                 beanValidator.setValidationGroups(validationGroups);
664             }
665         }
666     }
667 
668     /**
669      * Determine if the validator with the given validatorId should be added.
670      *
671      * @param validatorId The validatorId.
672      * @param facesContext The FacesContext.
673      * @param mctx the AbstractFaceletContext
674      * @param component The EditableValueHolder to which the validator should be added.
675      * @return true if the Validator should be added, false otherwise.
676      */
677     @SuppressWarnings("unchecked")
678     private boolean shouldAddDefaultValidator(FaceletContext ctx, FaceletCompositionContext mctx,
679                                               FacesContext facesContext,
680                                               EditableValueHolder component, 
681                                               String validatorId)
682     {
683         // check if the validatorId is on the exclusion list on the component
684         List<String> exclusionList 
685                 = (List<String>) ((UIComponent) component).getAttributes()
686                         .get(ValidatorTagHandlerDelegate.VALIDATOR_ID_EXCLUSION_LIST_KEY);
687         if (exclusionList != null)
688         {
689             for (String excludedId : exclusionList)
690             {
691                 if (excludedId.equals(validatorId))
692                 {
693                     return false;
694                 }
695             }
696         }
697         
698         // check if the validatorId is on the exclusion list on the stack
699         /*
700         Iterator<String> it = mctx.getExcludedValidatorIds();
701         if (it != null)
702         {            
703             while (it.hasNext())
704             {
705                 String excludedId = it.next();
706                 if (excludedId.equals(validatorId))
707                 {
708                     return false;
709                 }
710             }
711         }*/
712         Iterator<Map.Entry<String, EditableValueHolderAttachedObjectHandler>> enclosingValidatorIds =
713             mctx.getEnclosingValidatorIdsAndHandlers();
714         if (enclosingValidatorIds != null)
715         {
716             while (enclosingValidatorIds.hasNext())
717             {
718                 Map.Entry<String, EditableValueHolderAttachedObjectHandler> entry = enclosingValidatorIds.next();
719                 boolean validatorIdAvailable = entry.getKey() != null && !"".equals(entry.getKey());
720                 if (validatorIdAvailable && entry.getKey().equals(validatorId))
721                 {
722                     if (((ValidatorHandler)((FacesWrapper<ValidatorHandler>)entry.getValue()).getWrapped())
723                             .isDisabled(ctx))
724                     {
725                         return false;
726                     }
727                 }
728             }
729         }
730         
731         // Some extra rules are required for Bean Validation.
732         if (validatorId.equals(BeanValidator.VALIDATOR_ID))
733         {
734             if (!ExternalSpecifications.isBeanValidationAvailable())
735             {
736                 // the BeanValidator was added as a default-validator, but
737                 // bean validation is not available on the classpath.
738                 // --> log a warning about this scenario.
739                 log.log(Level.WARNING, "Bean validation is not available on the " +
740                         "classpath, thus the BeanValidator will not be added for " +
741                         "the component " + component);
742                 return false;
743             }
744         }
745 
746         // By default, all default validators should be added
747         return true;
748     }
749 
750     private void addEnclosingValidator(FaceletContext ctx, FaceletCompositionContext mctx, FacesContext context, 
751             EditableValueHolder component, String validatorId, 
752             EditableValueHolderAttachedObjectHandler attachedObjectHandler)
753     {
754         if (shouldAddEnclosingValidator(mctx, context, component, validatorId))
755         {
756             if (attachedObjectHandler != null)
757             {
758                 attachedObjectHandler.applyAttachedObject(context, (UIComponent) component);
759             }
760             else
761             {
762                 Validator validator = null;
763                 // create it
764                 validator = context.getApplication().createValidator(validatorId);
765 
766                 // special things to configure for a BeanValidator
767                 if (validator instanceof BeanValidator)
768                 {
769                     BeanValidator beanValidator = (BeanValidator) validator;
770                     
771                     // check the validationGroups
772                     String validationGroups =  beanValidator.getValidationGroups();
773                     if (validationGroups == null 
774                             || validationGroups.matches(BeanValidator.EMPTY_VALIDATION_GROUPS_PATTERN))
775                     {
776                         // no validationGroups available
777                         // --> get the validationGroups from the stack
778                         //String stackGroup = mctx.getFirstValidationGroupFromStack();
779                         //if (stackGroup != null)
780                         //{
781                         //    validationGroups = stackGroup;
782                         //}
783                         //else
784                         //{
785                             // no validationGroups on the stack
786                             // --> set the default validationGroup
787                             validationGroups = javax.validation.groups.Default.class.getName();
788                         //}
789                         beanValidator.setValidationGroups(validationGroups);
790                     }
791                 }
792                 
793                 // add the validator to the component
794                 component.addValidator(validator);
795             }
796         }
797     }
798 
799     /**
800      * Determine if the validator with the given validatorId should be added.
801      * 
802      * The difference here with shouldAddEnclosingValidator is the inner one has
803      * precedence over the outer one, so a disable="true" over the same outer 
804      * validator, the inner one should ignore this condition. 
805      * 
806      * @param mctx
807      * @param facesContext
808      * @param component
809      * @param validatorId
810      * @return
811      */
812     @SuppressWarnings("unchecked")
813     private boolean shouldAddEnclosingValidator(FaceletCompositionContext mctx,
814             FacesContext facesContext,
815             EditableValueHolder component, 
816             String validatorId)
817     {
818         // check if the validatorId is on the exclusion list on the component
819         List<String> exclusionList = (List<String>) ((UIComponent) component)
820                 .getAttributes()
821                 .get(ValidatorTagHandlerDelegate.VALIDATOR_ID_EXCLUSION_LIST_KEY);
822         if (exclusionList != null)
823         {
824             for (String excludedId : exclusionList)
825             {
826                 if (excludedId.equals(validatorId))
827                 {
828                     return false;
829                 }
830             }
831         }
832 
833         // Some extra rules are required for Bean Validation.
834         if (validatorId.equals(BeanValidator.VALIDATOR_ID))
835         {
836             if (!ExternalSpecifications.isBeanValidationAvailable())
837             {
838                 // the BeanValidator was added as a default-validator, but
839                 // bean validation is not available on the classpath.
840                 // --> log a warning about this scenario.
841                 log.log(Level.WARNING,
842                         "Bean validation is not available on the "
843                                 + "classpath, thus the BeanValidator will not be added for "
844                                 + "the component " + component);
845                 return false;
846             }
847         }
848 
849         // By default, all default validators should be added
850         return true;
851     }
852 }