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.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> or empty string, 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("NULL_FACESCONTEXT_OR_UICOMPONENT"));
120     }
121 
122     if (value == null)
123       return;
124 
125     ValidatorUtils.assertIsString(value, "'value' is not of type java.lang.String.");
126 
127     String theValue = (String)value;
128 
129     // Skip validating empty string
130     if(theValue.isEmpty())
131       return;
132 
133     if (getPattern() == null)
134       throw new NullPointerException(_LOG.getMessage("NULL_REGEXP_PATTERN"));
135 
136     // compile the regular expression if we haven't already.
137     // we cache the compiled regular expression because we can't cache
138     // the RE object, as it isn't thread safe.
139     if (_compiled == null)
140     {
141       try
142       {
143         _compiled = Pattern.compile(getPattern());
144       }
145       catch (PatternSyntaxException pse)
146       {
147         // compilation choked
148         throw pse;
149       }
150     }
151 
152     Matcher matcher = _compiled.matcher(theValue);
153     // the matched string has to be the same as the input
154     if (! matcher.matches())
155     {
156       throw new ValidatorException(_getNoMatchFoundMessage(context,
157                                                            component,
158                                                            theValue));
159     }
160   }
161 
162   @JSFProperty(istransient=true, tagExcluded=true)
163   public boolean isTransient()
164   {
165     return (_isTransient);
166   }
167 
168   public void setTransient(boolean transientValue)
169   {
170     _isTransient = transientValue;
171   }
172 
173   public Object saveState(FacesContext context)
174   {
175     return _facesBean.saveState(context);
176   }
177 
178   public void restoreState(FacesContext context, Object state)
179   {
180     _facesBean.restoreState(context, state);
181   }
182 
183 
184   /**
185    * <p>Set the {@link ValueExpression} used to calculate the value for the
186    * specified attribute if any.</p>
187    *
188    * @param name Name of the attribute for which to set a {@link ValueExpression}
189    * @param expression The {@link ValueExpression} to set, or <code>null</code>
190    *  to remove any currently set {@link ValueExpression}
191    *
192    * @exception NullPointerException if <code>name</code>
193    *  is <code>null</code>
194    * @exception IllegalArgumentException if <code>name</code> is not a valid
195    *            attribute of this converter
196    */
197   public void setValueExpression(String name, ValueExpression expression)
198   {
199     ValidatorUtils.setValueExpression(_facesBean, name, expression) ;
200   }
201 
202 
203   /**
204    * <p>Return the {@link ValueExpression} used to calculate the value for the
205    * specified attribute name, if any.</p>
206    *
207    * @param name Name of the attribute or property for which to retrieve a
208    *  {@link ValueExpression}
209    *
210    * @exception NullPointerException if <code>name</code>
211    *  is <code>null</code>
212    * @exception IllegalArgumentException if <code>name</code> is not a valid
213    * attribute of this converter
214    */
215   public ValueExpression getValueExpression(String name)
216   {
217     return ValidatorUtils.getValueExpression(_facesBean, name);
218   }
219 
220 
221   /**
222    * <p>Set the {@link ValueBinding} used to calculate the value for the
223    * specified attribute if any.</p>
224    *
225    * @param name Name of the attribute for which to set a {@link ValueBinding}
226    * @param binding The {@link ValueBinding} to set, or <code>null</code>
227    *  to remove any currently set {@link ValueBinding}
228    *
229    * @exception NullPointerException if <code>name</code>
230    *  is <code>null</code>
231    * @exception IllegalArgumentException if <code>name</code> is not a valid
232    *            attribute of this validator
233    * @deprecated
234    */
235   public void setValueBinding(String name, ValueBinding binding)
236   {
237     ValidatorUtils.setValueBinding(_facesBean, name, binding) ;
238   }
239 
240   /**
241    * <p>Return the {@link ValueBinding} used to calculate the value for the
242    * specified attribute name, if any.</p>
243    *
244    * @param name Name of the attribute or property for which to retrieve a
245    *  {@link ValueBinding}
246    *
247    * @exception NullPointerException if <code>name</code>
248    *  is <code>null</code>
249    * @exception IllegalArgumentException if <code>name</code> is not a valid
250    * attribute of this validator
251    * @deprecated
252    */
253   public ValueBinding getValueBinding(String name)
254   {
255     return ValidatorUtils.getValueBinding(_facesBean, name);
256   }
257 
258   /**
259    * <p>Compares this PatternValidator with the specified Object for
260    * equality.</p>
261    * @param object  Object to which this PatternValidator is to be compared.
262    * @return true if and only if the specified Object is a PatternValidator
263    * and if the values pattern and transient are equal.
264    */
265   @Override
266   public boolean equals(Object object)
267   {
268     if (this == object)
269       return true;
270 
271     if ( object instanceof RegExpValidator )
272     {
273       RegExpValidator other = (RegExpValidator) object;
274 
275       if ( this.isDisabled() == other.isDisabled() &&
276            this.isTransient() == other.isTransient() &&
277            ValidatorUtils.equals(getPattern(), other.getPattern()) &&
278            ValidatorUtils.equals(getMessageDetailNoMatch(),
279                                    other.getMessageDetailNoMatch())
280          )
281       {
282         return true;
283       }
284     }
285     return false;
286   }
287 
288   /**
289    * <p>Returns the hash code for this Validator.</p>
290    * @return a hash code value for this object.
291    */
292   @Override
293   public int hashCode()
294   {
295     int result = 17;
296     
297     String pattern = getPattern();
298     String noMesgDetail = getMessageDetailNoMatch();
299     result = 37 * result + (pattern == null? 0 : pattern.hashCode());
300     result = 37 * result + (isDisabled() ? 1 : 0);    
301     result = 37 * result + (isTransient() ? 0 : 1);
302     result = 37 * result + (noMesgDetail == null ? 0 : noMesgDetail.hashCode());
303     
304     return result;
305   }
306 
307   /**
308    * <p>Custom hint message.</p>
309    * Overrides default hint message
310    * @param hintPattern Custom hint message.
311    */
312   public void setHint(String hintPattern)
313   {
314     _facesBean.setProperty(_HINT_PATTERN_KEY, hintPattern);
315   }
316 
317   /**
318    * <p>Return custom hint message.</p>
319    * @return Custom hint message.
320    * @see  #setHint(String)
321    */
322   @JSFProperty(tagExcluded=true)
323   public String getHint()
324   {
325     Object obj = _facesBean.getProperty(_HINT_PATTERN_KEY);
326     return ComponentUtils.resolveString(obj);
327   }
328 
329   /**
330    * <p>Set the pattern value to be enforced by this {@link
331    * Validator}
332    * @param pattern to be enforced.
333    */
334   public void setPattern(String pattern)
335   {
336     String prevPattern = getPattern();
337     if ((prevPattern != null) && prevPattern.equals(pattern))
338       return;
339     if ((prevPattern == null) && (pattern == null))
340       return;
341 
342     _facesBean.setProperty(_PATTERN_KEY, pattern);
343     _compiled = null;
344   }
345 
346   /**
347    * <p>Return the pattern value to be enforced by this {@link
348    * Validator}
349    */
350   @JSFProperty
351   public String getPattern()
352   {
353     Object obj = _facesBean.getProperty(_PATTERN_KEY);
354     return ComponentUtils.resolveString(obj);
355   }
356 
357   /**
358    * <p>Custom error message to be used, for creating detail part of the
359    * {@link FacesMessage}, when value does not match the specified pattern.</p>
360    * Overrides detail message identified by message id {@link  #NO_MATCH_MESSAGE_ID}
361    * @param noMatchMessageDetail
362    */
363   public void setMessageDetailNoMatch(String noMatchMessageDetail)
364   {
365     _facesBean.setProperty(_NO_MATCH_MESSAGE_DETAIL_KEY, noMatchMessageDetail);
366   }
367 
368   /**
369    * <p>Return custom detail error message that was set for creating faces message,
370    * for values that do not match the specified pattern.</p>
371    * @return Custom error message
372    * @see #setMessageDetailNoMatch(String)
373    */
374   @JSFProperty
375   public String getMessageDetailNoMatch()
376   {
377     Object obj = _facesBean.getProperty(_NO_MATCH_MESSAGE_DETAIL_KEY);
378     return ComponentUtils.resolveString(obj);
379   }
380 
381   /**
382     * Return whether it is disabled.
383     * @return true if it's disabled and false if it's enabled. 
384     */ 
385   public void setDisabled(boolean isDisabled)
386   {
387     _facesBean.setProperty(_DISABLED_KEY, Boolean.valueOf(isDisabled));
388   }
389 
390   /**
391     * Return whether it is disabled.
392     * @return true if it's disabled and false if it's enabled. 
393     */  
394   public boolean isDisabled()
395   {
396     Boolean disabled = (Boolean) _facesBean.getProperty(_DISABLED_KEY);
397     
398     return (disabled != null) ? disabled.booleanValue() : false;
399   }    
400 
401   /**
402    * @todo custom message should be evaluated lazily and then be used for
403    * displaying message.
404    */
405   private FacesMessage _getNoMatchFoundMessage(
406     FacesContext context,
407     UIComponent component,
408     String value)
409   {
410     Object noMatchMsgDet = _getRawNoMatchMessageDetail();
411     Object label = ValidatorUtils.getComponentLabel(component);
412     Object[] params = {label, value, getPattern()};
413 
414     FacesMessage msg =
415         MessageFactory.getMessage(context, NO_MATCH_MESSAGE_ID,
416                                   noMatchMsgDet, params, label);
417       return msg;
418   }
419 
420   private Object _getRawNoMatchMessageDetail()
421   {
422     return _facesBean.getRawProperty(_NO_MATCH_MESSAGE_DETAIL_KEY);
423   }
424 
425 
426   private static final FacesBean.Type _TYPE = new FacesBean.Type();
427 
428   private static final PropertyKey _PATTERN_KEY
429     = _TYPE.registerKey("pattern", String.class);
430 
431   private static final PropertyKey _NO_MATCH_MESSAGE_DETAIL_KEY
432     = _TYPE.registerKey("messageDetailNoMatch", String.class);
433 
434   private static final PropertyKey  _HINT_PATTERN_KEY =
435     _TYPE.registerKey("hint", String.class);
436   
437   // Default is false
438   private static final PropertyKey _DISABLED_KEY =
439     _TYPE.registerKey("disabled", Boolean.class, Boolean.FALSE);
440 
441   private FacesBean _facesBean = ValidatorUtils.getFacesBean(_TYPE);
442 
443   private transient Pattern _compiled;
444 
445   private boolean _isTransient = false;
446 
447   private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(
448     RegExpValidator.class);
449 }