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.trinidad.validator;
20
21 import java.util.regex.Matcher;
22 import java.util.regex.Pattern;
23 import java.util.regex.PatternSyntaxException;
24
25 import javax.el.ValueExpression;
26
27 import javax.faces.application.FacesMessage;
28 import javax.faces.component.StateHolder;
29 import javax.faces.component.UIComponent;
30 import javax.faces.context.FacesContext;
31 import javax.faces.el.ValueBinding;
32 import javax.faces.validator.Validator;
33 import javax.faces.validator.ValidatorException;
34
35 import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFProperty;
36 import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFValidator;
37 import org.apache.myfaces.trinidad.bean.FacesBean;
38 import org.apache.myfaces.trinidad.bean.PropertyKey;
39 import org.apache.myfaces.trinidad.util.ComponentUtils;
40 import org.apache.myfaces.trinidad.util.MessageFactory;
41 import org.apache.myfaces.trinidad.logging.TrinidadLogger;
42
43 /**
44 <p><strong>RegExpValidator</strong> is a {@link javax.faces.validator.Validator} that checks
45 * the value of the corresponding component against specified pattern
46 * using Java regular expression syntax.
47 *
48 * The regular expression syntax accepted by the RegExpValidator class is
49 * same as mentioned in class {@link java.util.regex.Pattern} in package
50 * <code>java.util.regex</code>. The following algorithm is implemented:</p>
51 *
52 * <ul>
53 * <li>If the passed value is <code>null</code>, exit immediately.</li>
54 *
55 * <li>If a <code>pattern</code> property has been configured on this
56 * {@link javax.faces.validator.Validator}, check the component value against this pattern.
57 * If value does not match pattern throw a {@link ValidatorException}
58 * containing a NO_MATCH_MESSAGE_ID message.
59 * If <code>noMatchMessageDetail</code> is set, it is used for constructing faces
60 * message. The message can contain placeholders which will be replaced as
61 * specified in {@link #NO_MATCH_MESSAGE_ID}</li>
62 * </ul>
63 * @see #setMessageDetailNoMatch(String)
64 *
65 * @version $Name: $ ($Revision: adfrt/faces/adf-faces-api/src/main/java/oracle/adf/view/faces/validator/RegExpValidator.java#0 $) $Date: 10-nov-2005.19:08:34 $
66 */
67 @JSFValidator(configExcluded=true)
68 public class RegExpValidator implements StateHolder, Validator
69 {
70 /**
71 * <p>Standard validator id for this validator.</p>
72 */
73 public static final String VALIDATOR_ID = "org.apache.myfaces.trinidad.RegExp";
74
75 /**
76 * <p>The message identifier of the {@link FacesMessage}
77 * to be created if the match fails. The message format
78 * string for this message may optionally include a <code>{0}</code>,
79 * <code>{1}</code> and <code>{4}</code> placeholders, which will be replaced
80 * input value, label associated with the component and pattern respectively.</p>
81 */
82 public static final String NO_MATCH_MESSAGE_ID
83 = "org.apache.myfaces.trinidad.validator.RegExpValidator.NO_MATCH";
84
85 /**
86 * <p>Construct a RegExpValidator with no preconfigured pattern.</p>
87 */
88 public RegExpValidator()
89 {
90 super();
91 }
92
93 /**
94 * <p>Construct a RegExpValidator with preconfigured pattern.</p>
95 */
96 public RegExpValidator(String pattern)
97 {
98 setPattern(pattern);
99 }
100
101 /**
102 * @exception ValidatorException if validation fails
103 * @exception NullPointerException if <code>context</code>
104 * or <code>component</code> or <code>pattern</code> is <code>null</code>
105 * @exception IllegalArgumentException if <code>value</code> is not of type
106 * {@link java.lang.String}
107 */
108 public void validate(
109 FacesContext context,
110 UIComponent component,
111 Object value
112 ) throws ValidatorException
113 {
114 if (isDisabled())
115 return;
116
117 if ((context == null) || (component == null))
118 {
119 throw new NullPointerException(_LOG.getMessage(
120 "NULL_FACESCONTEXT_OR_UICOMPONENT"));
121 }
122
123 if ( value != null)
124 {
125 ValidatorUtils.assertIsString(value,
126 "'value' is not of type java.lang.String.");
127
128 if (getPattern() == null)
129 throw new NullPointerException(_LOG.getMessage(
130 "NULL_REGEXP_PATTERN"));
131
132 // compile the regular expression if we haven't already.
133 // we cache the compiled regular expression because we can't cache
134 // the RE object, as it isn't thread safe.
135 if (_compiled == null)
136 {
137 try
138 {
139 _compiled = Pattern.compile(getPattern());
140 }
141 catch (PatternSyntaxException pse)
142 {
143 // compilation choked
144 throw pse;
145 }
146 }
147 String theValue = (String)value;
148 Matcher matcher = _compiled.matcher(theValue);
149 // the matched string has to be the same as the input
150 if (! matcher.matches())
151 {
152 throw new ValidatorException(_getNoMatchFoundMessage(context,
153 component,
154 theValue));
155 }
156 }
157 }
158
159 @JSFProperty(istransient=true, tagExcluded=true)
160 public boolean isTransient()
161 {
162 return (_isTransient);
163 }
164
165 public void setTransient(boolean transientValue)
166 {
167 _isTransient = transientValue;
168 }
169
170 public Object saveState(FacesContext context)
171 {
172 return _facesBean.saveState(context);
173 }
174
175 public void restoreState(FacesContext context, Object state)
176 {
177 _facesBean.restoreState(context, state);
178 }
179
180
181 /**
182 * <p>Set the {@link ValueExpression} used to calculate the value for the
183 * specified attribute if any.</p>
184 *
185 * @param name Name of the attribute for which to set a {@link ValueExpression}
186 * @param expression The {@link ValueExpression} to set, or <code>null</code>
187 * to remove any currently set {@link ValueExpression}
188 *
189 * @exception NullPointerException if <code>name</code>
190 * is <code>null</code>
191 * @exception IllegalArgumentException if <code>name</code> is not a valid
192 * attribute of this converter
193 */
194 public void setValueExpression(String name, ValueExpression expression)
195 {
196 ValidatorUtils.setValueExpression(_facesBean, name, expression) ;
197 }
198
199
200 /**
201 * <p>Return the {@link ValueExpression} used to calculate the value for the
202 * specified attribute name, if any.</p>
203 *
204 * @param name Name of the attribute or property for which to retrieve a
205 * {@link ValueExpression}
206 *
207 * @exception NullPointerException if <code>name</code>
208 * is <code>null</code>
209 * @exception IllegalArgumentException if <code>name</code> is not a valid
210 * attribute of this converter
211 */
212 public ValueExpression getValueExpression(String name)
213 {
214 return ValidatorUtils.getValueExpression(_facesBean, name);
215 }
216
217
218 /**
219 * <p>Set the {@link ValueBinding} used to calculate the value for the
220 * specified attribute if any.</p>
221 *
222 * @param name Name of the attribute for which to set a {@link ValueBinding}
223 * @param binding The {@link ValueBinding} to set, or <code>null</code>
224 * to remove any currently set {@link ValueBinding}
225 *
226 * @exception NullPointerException if <code>name</code>
227 * is <code>null</code>
228 * @exception IllegalArgumentException if <code>name</code> is not a valid
229 * attribute of this validator
230 * @deprecated
231 */
232 public void setValueBinding(String name, ValueBinding binding)
233 {
234 ValidatorUtils.setValueBinding(_facesBean, name, binding) ;
235 }
236
237 /**
238 * <p>Return the {@link ValueBinding} used to calculate the value for the
239 * specified attribute name, if any.</p>
240 *
241 * @param name Name of the attribute or property for which to retrieve a
242 * {@link ValueBinding}
243 *
244 * @exception NullPointerException if <code>name</code>
245 * is <code>null</code>
246 * @exception IllegalArgumentException if <code>name</code> is not a valid
247 * attribute of this validator
248 * @deprecated
249 */
250 public ValueBinding getValueBinding(String name)
251 {
252 return ValidatorUtils.getValueBinding(_facesBean, name);
253 }
254
255 /**
256 * <p>Compares this PatternValidator with the specified Object for
257 * equality.</p>
258 * @param object Object to which this PatternValidator is to be compared.
259 * @return true if and only if the specified Object is a PatternValidator
260 * and if the values pattern and transient are equal.
261 */
262 @Override
263 public boolean equals(Object object)
264 {
265 if (this == object)
266 return true;
267
268 if ( object instanceof RegExpValidator )
269 {
270 RegExpValidator other = (RegExpValidator) object;
271
272 if ( this.isDisabled() == other.isDisabled() &&
273 this.isTransient() == other.isTransient() &&
274 ValidatorUtils.equals(getPattern(), other.getPattern()) &&
275 ValidatorUtils.equals(getMessageDetailNoMatch(),
276 other.getMessageDetailNoMatch())
277 )
278 {
279 return true;
280 }
281 }
282 return false;
283 }
284
285 /**
286 * <p>Returns the hash code for this Validator.</p>
287 * @return a hash code value for this object.
288 */
289 @Override
290 public int hashCode()
291 {
292 int result = 17;
293
294 String pattern = getPattern();
295 String noMesgDetail = getMessageDetailNoMatch();
296 result = 37 * result + (pattern == null? 0 : pattern.hashCode());
297 result = 37 * result + (isDisabled() ? 1 : 0);
298 result = 37 * result + (isTransient() ? 0 : 1);
299 result = 37 * result + (noMesgDetail == null ? 0 : noMesgDetail.hashCode());
300
301 return result;
302 }
303
304 /**
305 * <p>Custom hint message.</p>
306 * Overrides default hint message
307 * @param hintPattern Custom hint message.
308 */
309 public void setHint(String hintPattern)
310 {
311 _facesBean.setProperty(_HINT_PATTERN_KEY, hintPattern);
312 }
313
314 /**
315 * <p>Return custom hint message.</p>
316 * @return Custom hint message.
317 * @see #setHint(String)
318 */
319 @JSFProperty(tagExcluded=true)
320 public String getHint()
321 {
322 Object obj = _facesBean.getProperty(_HINT_PATTERN_KEY);
323 return ComponentUtils.resolveString(obj);
324 }
325
326 /**
327 * <p>Set the pattern value to be enforced by this {@link
328 * Validator}
329 * @param pattern to be enforced.
330 */
331 public void setPattern(String pattern)
332 {
333 String prevPattern = getPattern();
334 if ((prevPattern != null) && prevPattern.equals(pattern))
335 return;
336 if ((prevPattern == null) && (pattern == null))
337 return;
338
339 _facesBean.setProperty(_PATTERN_KEY, pattern);
340 _compiled = null;
341 }
342
343 /**
344 * <p>Return the pattern value to be enforced by this {@link
345 * Validator}
346 */
347 @JSFProperty
348 public String getPattern()
349 {
350 Object obj = _facesBean.getProperty(_PATTERN_KEY);
351 return ComponentUtils.resolveString(obj);
352 }
353
354 /**
355 * <p>Custom error message to be used, for creating detail part of the
356 * {@link FacesMessage}, when value does not match the specified pattern.</p>
357 * Overrides detail message identified by message id {@link #NO_MATCH_MESSAGE_ID}
358 * @param noMatchMessageDetail
359 */
360 public void setMessageDetailNoMatch(String noMatchMessageDetail)
361 {
362 _facesBean.setProperty(_NO_MATCH_MESSAGE_DETAIL_KEY, noMatchMessageDetail);
363 }
364
365 /**
366 * <p>Return custom detail error message that was set for creating faces message,
367 * for values that do not match the specified pattern.</p>
368 * @return Custom error message
369 * @see #setMessageDetailNoMatch(String)
370 */
371 @JSFProperty
372 public String getMessageDetailNoMatch()
373 {
374 Object obj = _facesBean.getProperty(_NO_MATCH_MESSAGE_DETAIL_KEY);
375 return ComponentUtils.resolveString(obj);
376 }
377
378 /**
379 * Return whether it is disabled.
380 * @return true if it's disabled and false if it's enabled.
381 */
382 public void setDisabled(boolean isDisabled)
383 {
384 _facesBean.setProperty(_DISABLED_KEY, Boolean.valueOf(isDisabled));
385 }
386
387 /**
388 * Return whether it is disabled.
389 * @return true if it's disabled and false if it's enabled.
390 */
391 public boolean isDisabled()
392 {
393 Boolean disabled = (Boolean) _facesBean.getProperty(_DISABLED_KEY);
394
395 return (disabled != null) ? disabled.booleanValue() : false;
396 }
397
398 /**
399 * @todo custom message should be evaluated lazily and then be used for
400 * displaying message.
401 */
402 private FacesMessage _getNoMatchFoundMessage(
403 FacesContext context,
404 UIComponent component,
405 String value)
406 {
407 Object noMatchMsgDet = _getRawNoMatchMessageDetail();
408 Object label = ValidatorUtils.getComponentLabel(component);
409 Object[] params = {label, value, getPattern()};
410
411 FacesMessage msg =
412 MessageFactory.getMessage(context, NO_MATCH_MESSAGE_ID,
413 noMatchMsgDet, params, label);
414 return msg;
415 }
416
417 private Object _getRawNoMatchMessageDetail()
418 {
419 return _facesBean.getRawProperty(_NO_MATCH_MESSAGE_DETAIL_KEY);
420 }
421
422
423 private static final FacesBean.Type _TYPE = new FacesBean.Type();
424
425 private static final PropertyKey _PATTERN_KEY
426 = _TYPE.registerKey("pattern", String.class);
427
428 private static final PropertyKey _NO_MATCH_MESSAGE_DETAIL_KEY
429 = _TYPE.registerKey("messageDetailNoMatch", String.class);
430
431 private static final PropertyKey _HINT_PATTERN_KEY =
432 _TYPE.registerKey("hint", String.class);
433
434 // Default is false
435 private static final PropertyKey _DISABLED_KEY =
436 _TYPE.registerKey("disabled", Boolean.class, Boolean.FALSE);
437
438 private FacesBean _facesBean = ValidatorUtils.getFacesBean(_TYPE);
439
440 private transient Pattern _compiled;
441
442 private boolean _isTransient = false;
443
444 private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(
445 RegExpValidator.class);
446 }