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>, 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 }