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