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