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