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.ArrayList;
23  import java.util.List;
24  
25  import javax.el.ValueExpression;
26  import javax.faces.FacesWrapper;
27  import javax.faces.component.EditableValueHolder;
28  import javax.faces.component.UIComponent;
29  import javax.faces.context.FacesContext;
30  import javax.faces.validator.Validator;
31  import javax.faces.view.EditableValueHolderAttachedObjectHandler;
32  import javax.faces.view.facelets.ComponentHandler;
33  import javax.faces.view.facelets.FaceletContext;
34  import javax.faces.view.facelets.MetaRuleset;
35  import javax.faces.view.facelets.TagAttribute;
36  import javax.faces.view.facelets.TagException;
37  import javax.faces.view.facelets.TagHandlerDelegate;
38  import javax.faces.view.facelets.ValidatorHandler;
39  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
40  
41  import org.apache.myfaces.shared.renderkit.JSFAttr;
42  import org.apache.myfaces.shared.util.WebConfigParamUtils;
43  import org.apache.myfaces.view.facelets.FaceletCompositionContext;
44  import org.apache.myfaces.view.facelets.compiler.FaceletsCompilerUtils;
45  import org.apache.myfaces.view.facelets.tag.MetaRulesetImpl;
46  
47  /**
48   * Handles setting a Validator instance on a EditableValueHolder. Will wire all attributes set to the Validator instance
49   * created/fetched. Uses the "binding" attribute for grabbing instances to apply attributes to. <p> Will only
50   * set/create Validator is the passed UIComponent's parent is null, signifying that it wasn't restored from an existing
51   * tree.</p>
52   * 
53   * @author Leonardo Uribe (latest modification by $Author$)
54   * @version $Revision$ $Date$
55   *
56   * @since 2.0
57   */
58  public class ValidatorTagHandlerDelegate extends TagHandlerDelegate 
59      implements EditableValueHolderAttachedObjectHandler, FacesWrapper<ValidatorHandler>
60  {
61      
62      /**
63       * if &lt;f:validateBean&gt; has no children and its disabled attribute is true,
64       * its validatorId will be added to the exclusion list stored under
65       * this key on the parent UIComponent.
66       */
67      public final static String VALIDATOR_ID_EXCLUSION_LIST_KEY
68              = "org.apache.myfaces.validator.VALIDATOR_ID_EXCLUSION_LIST";
69      
70      /**
71       * Enforce f:validateBean to be called first before any JSF validator.
72       */
73      @JSFWebConfigParam(defaultValue="false", expectedValues="true, false", since = "2.2.10", group="validation")
74      private final static String BEAN_BEFORE_JSF_VALIDATION
75              = "org.apache.myfaces.validator.BEAN_BEFORE_JSF_VALIDATION";
76      
77      private final static String BEAN_BEFORE_JSF_PROPERTY = "oam.beanBeforeJsf";
78      
79      private ValidatorHandler _delegate;
80      
81      /**
82       * true - this tag has children
83       * false - this tag is a leave
84       */
85      private final boolean _wrapMode;
86      
87      private Boolean _beanBeforeJsfValidation;
88      
89      public ValidatorTagHandlerDelegate(ValidatorHandler delegate)
90      {
91          _delegate = delegate;
92  
93          // According to jsf 2.0 spec section 10.4.1.4
94          // this tag can be used as a leave within an EditableValueHolder
95          // or as a container to provide validator information for all 
96          // EditableValueHolder-children (and grandchildren and ...)
97          // (this behavior is analog to <f:ajax>)
98          // --> Determine if we have children:
99          _wrapMode = FaceletsCompilerUtils.hasChildren(_delegate.getValidatorConfig());
100         _beanBeforeJsfValidation = null;
101     }
102 
103     @Override
104     public void apply(FaceletContext ctx, UIComponent parent) throws IOException
105     {
106         // we need methods from AbstractFaceletContext
107         FaceletCompositionContext mctx = FaceletCompositionContext.getCurrentInstance(ctx);
108 
109         if (_wrapMode)
110         {
111             // the tag has children --> provide validator information for all children
112             
113             // FIXME the spec says we should save the validation groups in an attribute
114             // on the parent UIComponent, but this will be a problem in the following scenario:
115             // <h:form>
116             //     <f:validateBean>
117             //         <h:inputText />
118             //     </f:validateBean>
119             //     <h:inputText />
120             // </h:form>
121             // because the validator would also be applied to the second h:inputText,
122             // which it should not, on my opinion. In addition, mojarra also does not
123             // attach the validator to the second h:inputText in this scenario (blackbox test).
124             // So I use the same way as f:ajax for this problem. -=Jakob Korherr=-
125             
126             String validatorId = _delegate.getValidatorConfig().getValidatorId();
127             /*
128             boolean disabled = _delegate.isDisabled(ctx);
129             if (disabled)
130             {
131                 // the validator is disabled --> add its id to the exclusion stack
132                 boolean validatorIdAvailable = validatorId != null && !"".equals(validatorId);
133                 try
134                 {
135                     if (validatorIdAvailable)
136                     {
137                         mctx.pushExcludedValidatorIdToStack(validatorId);
138                     }
139                     _delegate.getValidatorConfig().getNextHandler().apply(ctx, parent);
140                 }
141                 finally
142                 {
143                     if (validatorIdAvailable)
144                     {
145                         mctx.popExcludedValidatorIdToStack();
146                     }
147                 }
148             }
149             else
150             {*/
151                 // the validator is enabled 
152                 // --> add the validation groups and the validatorId to the stack
153                 //String groups = getValidationGroups(ctx);
154                 // spec: don't save the validation groups string if it is null or empty string
155                 //boolean groupsAvailable = groups != null 
156                         //&& !groups.matches(BeanValidator.EMPTY_VALIDATION_GROUPS_PATTERN);
157                 //try
158                 //{
159                     //if (groupsAvailable)
160                     //{
161                     //    mctx.pushValidationGroupsToStack(groups);
162                     //}
163                     try
164                     {
165                         mctx.pushEnclosingValidatorIdToStack(validatorId, this);
166                         _delegate.applyNextHandler(ctx, parent);
167                     }
168                     finally
169                     {
170                         mctx.popEnclosingValidatorIdToStack();
171                     }
172                 //}
173                 //finally
174                 //{
175                     //if (groupsAvailable)
176                     //{
177                         //mctx.popValidationGroupsToStack();
178                     //}
179                 //}
180             /*}*/
181         }
182         else
183         {
184             // Apply only if we are creating a new component
185             if (!ComponentHandler.isNew(parent))
186             {
187                 return;
188             }
189 
190             // the tag is a leave --> attach validator to parent
191             if (parent instanceof EditableValueHolder)
192             {
193                 applyAttachedObject(ctx.getFacesContext(), parent);
194             }
195             else if (UIComponent.isCompositeComponent(parent))
196             {
197                 if (getFor() == null)
198                 {
199                     throw new TagException(_delegate.getTag(), "is nested inside a composite component"
200                             + " but does not have a for attribute.");
201                 }
202                 mctx.addAttachedObjectHandler(parent, _delegate);
203             }
204             else
205             {
206                 throw new TagException(_delegate.getTag(),
207                         "Parent not composite component or an instance of EditableValueHolder: " + parent);
208             }
209         }
210     }
211 
212     /**
213      * Template method for creating a Validator instance
214      * 
215      * @param ctx FaceletContext to use
216      * @return a new Validator instance
217      */
218     protected Validator createValidator(FaceletContext ctx)
219     {
220         if (_delegate.getValidatorId(ctx) == null)
221         {
222             throw new TagException(_delegate.getTag(), "Default behavior invoked of requiring " +
223                     "a validator-id passed in the constructor, must override ValidateHandler(ValidatorConfig)");
224         }
225         return ctx.getFacesContext().getApplication().createValidator(_delegate.getValidatorId(ctx));
226     }
227 
228     @Override
229     public MetaRuleset createMetaRuleset(Class type)
230     {
231         MetaRuleset metaRuleset = new MetaRulesetImpl(_delegate.getTag(), type);
232         
233         // ignore binding and disabled, because they are handled by DelegatingMetaTagHandler
234         metaRuleset.ignore(JSFAttr.BINDING_ATTR).ignore(JSFAttr.DISABLED_ATTR);
235         // ignore for, because it is handled by FaceletsAttachedObjectHandler
236         metaRuleset.ignore(JSFAttr.FOR_ATTR);
237         
238         return metaRuleset;
239     }
240 
241     @SuppressWarnings("unchecked")
242     public void applyAttachedObject(FacesContext context, UIComponent parent)
243     {
244         // Retrieve the current FaceletContext from FacesContext object
245         FaceletContext faceletContext = (FaceletContext) context.getAttributes().get(
246                 FaceletContext.FACELET_CONTEXT_KEY);
247         
248         // spec: if the disabled attribute is true, the validator should not be added.
249         // in addition, the validatorId, if present, should be added to an exclusion
250         // list on the parent component to prevent a default validator with the same
251         // id from beeing registered on the component.
252         if (_delegate.isDisabled(faceletContext))
253         {
254             // tag is disabled --> add its validatorId to the parent's exclusion list
255             String validatorId = _delegate.getValidatorConfig().getValidatorId();
256             if (validatorId != null && !"".equals(validatorId))
257             {
258                 List<String> exclusionList = (List<String>) parent.getAttributes()
259                         .get(VALIDATOR_ID_EXCLUSION_LIST_KEY);
260                 if (exclusionList == null)
261                 {
262                     exclusionList = new ArrayList<String>();
263                     parent.getAttributes().put(VALIDATOR_ID_EXCLUSION_LIST_KEY, exclusionList);
264                 }
265                 exclusionList.add(validatorId);
266             }
267         }
268         else
269         {
270             // tag is enabled --> create the validator and attach it
271             
272             // cast to a ValueHolder
273             EditableValueHolder evh = (EditableValueHolder) parent;
274             ValueExpression ve = null;
275             Validator v = null;
276             if (_delegate.getBinding() != null)
277             {
278                 ve = _delegate.getBinding().getValueExpression(faceletContext, Validator.class);
279                 v = (Validator) ve.getValue(faceletContext);
280             }
281             if (v == null)
282             {
283                 v = this.createValidator(faceletContext);
284                 if (ve != null)
285                 {
286                     ve.setValue(faceletContext, v);
287                 }
288             }
289             if (v == null)
290             {
291                 throw new TagException(_delegate.getTag(), "No Validator was created");
292             }
293             _delegate.setAttributes(faceletContext, v);
294             if (shouldBeanBeforeJsfValidationEnabled(context))
295             {
296                 parent.getAttributes().put(BEAN_BEFORE_JSF_PROPERTY, Boolean.TRUE);
297             }
298             evh.addValidator(v); 
299         }
300     }
301     
302     private boolean shouldBeanBeforeJsfValidationEnabled(FacesContext context)
303     {
304         if (_beanBeforeJsfValidation == null)
305         {
306             _beanBeforeJsfValidation = WebConfigParamUtils.getBooleanInitParameter(context.getExternalContext(),
307                     BEAN_BEFORE_JSF_VALIDATION, false);
308         }
309         return _beanBeforeJsfValidation;
310     }
311             
312 
313     public String getFor()
314     {
315         TagAttribute forAttribute = _delegate.getTagAttribute("for");
316         
317         if (forAttribute == null)
318         {
319             return null;
320         }
321         else
322         {
323             return forAttribute.getValue();
324         }
325     }
326     
327     public String getValidationGroups(FaceletContext ctx)
328     {
329         TagAttribute attribute = _delegate.getTagAttribute("validationGroups");
330         
331         if (attribute == null)
332         {
333             return null;
334         }
335         else
336         {
337             return attribute.getValue(ctx);
338         }
339     }
340 
341     public ValidatorHandler getWrapped()
342     {
343         return _delegate;
344     }
345 
346 }