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.io.UnsupportedEncodingException;
22  
23  import java.nio.charset.IllegalCharsetNameException;
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  /**
45   * Validator for validating the byte length of strings.
46   * If encoding is not specified, defaults to ISO-8859-1
47   */
48  /**
49   * <p><strong>ByteLengthValidator</strong> is a {@link Validator} that checks
50   * the value of the corresponding component for its byte length for the set
51   * character encoding. The following algorithm is implemented:</p>
52   * <ul>
53   * <li>If the passed value is <code>null</code>, exit immediately.</li>
54   * <li>If the current component value is not of String type throw
55   *      IllegalArgumentException
56   * <li>If both <code>encoding</code> and <code>maximum</code> property
57   *     has been configured on this {@link Validator}, check the component
58   *     value byte length against the maximum. If the component value byte length
59   *     is greater than this specified maximum, throw a {@link ValidatorException}
60   *     containing a MAXIMUM_MESSAGE_ID message.</li>
61   * <li>If only <code>maximum</code> property has been configured on this
62   *     {@link Validator}, check the component value against
63   *     this limit defaulting the encoding to be <code>iso-8859-1</code>.
64   *     If the component value length is greater than the specified maximum,
65   *     throw a {@link ValidatorException} containing a MAXIMUM_MESSAGE_ID
66   *     message.</li>
67   * <li>If a <code>encoding</code> property has been configured on this
68   *     {@link Validator}, and if it is not a valid Java encoding, then throws a
69   *     {@link java.nio.charset.IllegalCharsetNameException}</li>
70   *
71   * <li>If  <code>maximumMessageDetail</code> is set, it is used for constructing
72   *     faces message, when validation fails. The message can contain placeholders
73   *     which will be replaced as specified in {@link #MAXIMUM_MESSAGE_ID}
74   * </li>
75   * </ul>
76   * @see #setMessageDetailMaximum(String)
77   *
78   * @version $Name:  $ ($Revision: adfrt/faces/adf-faces-api/src/main/java/oracle/adf/view/faces/validator/ByteLengthValidator.java#0 $) $Date: 10-nov-2005.19:08:32 $
79   */
80  @JSFValidator(configExcluded=true)
81  public class ByteLengthValidator  implements StateHolder, Validator
82  {
83  
84    /**
85     * <p>The message identifier of the {@link FacesMessage} to be created if
86     * the maximum byte length check fails.  The message format string for this
87     * message may optionally include  <code>{0}</code>, <code>{1}</code> and
88     * <code>{2}</code> placeholders, which will be replaced by input value, label
89     * associated with the component and the maximum bytes respectively.<p>
90     */
91    public static final String MAXIMUM_MESSAGE_ID =
92      "org.apache.myfaces.trinidad.validator.ByteLengthValidator.MAXIMUM";
93  
94    /**
95     *  <p>Standard validator id for this validator.</p>
96     */
97    public static final String VALIDATOR_ID = "org.apache.myfaces.trinidad.ByteLength";
98  
99  
100   /**
101    * <p>Construct a {@link Validator} with <code>iso-8859-1</code> as the encoding
102    * and <code>zero</code> as the maximum bytes allowed.</p>
103    */
104   public ByteLengthValidator()
105   {
106     setEncoding("iso-8859-1");
107   }
108 
109   /**
110    * <p>Construct a {@link Validator} with the specified preconfigured
111    * values.</p>
112    * @param maximum the maximum number of bytes allowed.
113    * @param encoding the Java character set encoding. This must be
114    *        an encoding supported by Java.
115    */
116   public ByteLengthValidator(int maximum, String encoding)
117   {
118     super();
119     setMaximum(maximum);
120     setEncoding(encoding);
121   }
122 
123   /**
124    * <p>Set the character encoding for this {@link Validator}.</p>
125    *
126    * @param encoding The character encoding.
127    */
128   public void setEncoding(String encoding)
129   {
130     _facesBean.setProperty(_ENCODING_KEY, encoding);
131   }
132 
133   /**
134    * <p>Return the character encoding set for this {@link Validator} or
135    * <code>iso-8859-1</code> if it has not been set.</p>
136    */
137   @JSFProperty(defaultValue="iso-8859-1")
138   public String getEncoding()
139   {
140     Object encoding = _facesBean.getProperty(_ENCODING_KEY);
141     return ComponentUtils.resolveString(encoding);
142   }
143 
144   /**
145    * <p>Set the maximum bytes to be enforced by this {@link Validator}.</p>
146    *
147    * @param maximum The new maximum value
148    *
149    */
150   public void setMaximum(int maximum)
151   {
152     _facesBean.setProperty(_MAXIMUM_KEY, Integer.valueOf(maximum));
153   }
154 
155   /**
156    * <p>Return the maximum bytes to be enforced by this {@link
157    * Validator} or <code>zero</code> if it has not been
158    * set.</p>
159    */
160   @JSFProperty
161   public int getMaximum()
162   {
163     return ComponentUtils.resolveInteger(_facesBean.getProperty(_MAXIMUM_KEY));
164   }
165 
166   /**
167    * <p>Custom error message to be used, for creating detail part of the
168    * {@link FacesMessage},  when users input exceeds the maximum byte length.</p>
169    * Overrides detail message identified by message id {@link #MAXIMUM_MESSAGE_ID}
170    * @param maximumMessageDetail Custom error message.
171    */
172   public void setMessageDetailMaximum(String maximumMessageDetail)
173   {
174     _facesBean.setProperty(_MAXIMUM_MESSAGE_DETAIL_KEY, maximumMessageDetail);
175   }
176 
177   /**
178    * <p>Return custom detail error message that was set for creating {@link FacesMessage},
179    * for values that exceeds the maximum byte length.</p>
180    * @return Custom error message.
181    * @see  #setMessageDetailMaximum(String)
182    */
183   @JSFProperty
184   public String getMessageDetailMaximum()
185   {
186     Object obj = _facesBean.getProperty(_MAXIMUM_MESSAGE_DETAIL_KEY);
187     return ComponentUtils.resolveString(obj);
188   }
189 
190   /**
191    * <p>Custom hint message.</p>
192    * Overrides default hint message
193    * @param hintMaximum Custom hint message.
194    */
195   public void setHintMaximum(String hintMaximum)
196   {
197     _facesBean.setProperty(_HINT_MAXIMUM_KEY, hintMaximum);
198   }
199 
200   /**
201    * <p>Return custom hint message.</p>
202    * @return Custom hint message.
203    * @see  #setHintMaximum(String)
204    */
205   @JSFProperty(tagExcluded=true)
206   public String getHintMaximum()
207   {
208     Object obj = _facesBean.getProperty(_HINT_MAXIMUM_KEY);
209     return ComponentUtils.resolveString(obj);
210   }
211 
212   /**
213    * <p>Validates unless it is too long, in which case throws
214    * ValidatorException.</p>
215    * @exception ValidatorException if validation fails
216    * @exception NullPointerException if <code>context</code>
217    * @exception IllegalCharsetNameException if <code>encoding</code> is
218    * @exception IllegalArgumentException if <code>value</code> is not of type
219    *            {@link java.lang.String}
220    * <code>unsupported</code>
221    *  or <code>component</code> is <code>null</code>
222    */
223   public void validate(
224     FacesContext context,
225     UIComponent component,
226     Object value
227     ) throws ValidatorException
228   {
229     if (isDisabled())
230       return;
231     
232     if ((context == null) || (component == null))
233     {
234       throw new NullPointerException(_LOG.getMessage(
235         "NULL_FACESCONTEXT_OR_UICOMPONENT"));
236     }
237 
238     if (value != null)
239     {
240       ValidatorUtils.assertIsString(value,
241                                     "'value' is not of type java.lang.String.");
242       String theValue  = (String)value;
243 
244       int maxBytes = getMaximum();
245       try
246       {
247         byte[] bytes = theValue.getBytes(getEncoding());
248         if (bytes.length > maxBytes)
249           throw new ValidatorException(
250             getLengthValidationFailureMessage(context, component, theValue));
251 
252       }
253       catch (UnsupportedEncodingException uee)
254       {
255         throw new IllegalCharsetNameException(_LOG.getMessage(
256           "ENCODING_NOT_SUPPORTED_BY_JVM", getEncoding()));
257       }
258     }
259   }
260 
261   public Object saveState(FacesContext context)
262   {
263     return _facesBean.saveState(context);
264   }
265 
266   public void restoreState(FacesContext context, Object state)
267   {
268     _facesBean.restoreState(context, state);
269   }
270 
271   @JSFProperty(istransient=true, tagExcluded=true)
272   public boolean isTransient()
273   {
274     return (_isTransient);
275   }
276 
277   public void setTransient(boolean transientValue)
278   {
279     _isTransient = transientValue;
280   }
281 
282 
283   /**
284    * <p>Set the {@link ValueExpression} used to calculate the value for the
285    * specified attribute if any.</p>
286    *
287    * @param name Name of the attribute for which to set a {@link ValueExpression}
288    * @param expression The {@link ValueExpression} to set, or <code>null</code>
289    *  to remove any currently set {@link ValueExpression}
290    *
291    * @exception NullPointerException if <code>name</code>
292    *  is <code>null</code>
293    * @exception IllegalArgumentException if <code>name</code> is not a valid
294    *            attribute of this converter
295    */
296   public void setValueExpression(String name, ValueExpression expression)
297   {
298     ValidatorUtils.setValueExpression(_facesBean, name, expression) ;
299   }
300 
301 
302   /**
303    * <p>Return the {@link ValueExpression} used to calculate the value for the
304    * specified attribute name, if any.</p>
305    *
306    * @param name Name of the attribute or property for which to retrieve a
307    *  {@link ValueExpression}
308    *
309    * @exception NullPointerException if <code>name</code>
310    *  is <code>null</code>
311    * @exception IllegalArgumentException if <code>name</code> is not a valid
312    * attribute of this converter
313    */
314   public ValueExpression getValueExpression(String name)
315   {
316     return ValidatorUtils.getValueExpression(_facesBean, name);
317   }
318 
319   /**
320    * <p>Set the {@link ValueBinding} used to calculate the value for the
321    * specified attribute if any.</p>
322    *
323    * @param name Name of the attribute for which to set a {@link ValueBinding}
324    * @param binding The {@link ValueBinding} to set, or <code>null</code>
325    *  to remove any currently set {@link ValueBinding}
326    *
327    * @exception NullPointerException if <code>name</code>
328    *  is <code>null</code>
329    * @exception IllegalArgumentException if <code>name</code> is not a valid
330    *            attribute of this validator
331    * @deprecated
332    */
333   public void setValueBinding(String name, ValueBinding binding)
334   {
335     ValidatorUtils.setValueBinding(_facesBean, name, binding) ;
336   }
337 
338   /**
339    * <p>Return the {@link ValueBinding} used to calculate the value for the
340    * specified attribute name, if any.</p>
341    *
342    * @param name Name of the attribute or property for which to retrieve a
343    *  {@link ValueBinding}
344    *
345    * @exception NullPointerException if <code>name</code>
346    *  is <code>null</code>
347    * @exception IllegalArgumentException if <code>name</code> is not a valid
348    * attribute of this validator
349    * @deprecated
350    */
351   public ValueBinding getValueBinding(String name)
352   {
353     return ValidatorUtils.getValueBinding(_facesBean, name);
354   }
355 
356   /**
357    * <p>Compares this ByteLengthValidator with the specified Object for
358    * equality.</p>
359    * @param object  Object to which this ByteLengthValidator is to be compared.
360    * @return true if and only if the specified Object is a ByteLengthValidator
361    * and if the values encoding, maximum and transient are equal.
362    */
363   @Override
364   public boolean equals(Object object)
365   {
366 
367     if (this == object)
368       return true;
369 
370     if ( object instanceof ByteLengthValidator )
371     {
372       ByteLengthValidator other = (ByteLengthValidator) object;
373       String encoding = getEncoding();
374       String otherEncoding = other.getEncoding();
375       String otherMsgMaxDet = other.getMessageDetailMaximum();
376       String msgMaxDet = getMessageDetailMaximum();
377 
378       if ( this.isDisabled() == other.isDisabled() &&
379            this.isTransient() == other.isTransient() &&
380             ValidatorUtils.equals(encoding, otherEncoding) &&
381             ValidatorUtils.equals(msgMaxDet, otherMsgMaxDet) &&
382             (getMaximum() == other.getMaximum())
383          )
384       {
385         return true;
386       }
387     }
388     return false;
389  }
390 
391  /**
392   * <p>Returns the hash code for this Validator.</p>
393   * @return a hash code value for this object.
394   */
395   @Override
396   public int hashCode()
397   {
398     int result = 17;
399     String maximumMsgDet = getMessageDetailMaximum();
400     String encoding = getEncoding();
401     result = 37 * result + (encoding == null? 0 : encoding.hashCode());
402     result = 37 * result + (_isTransient ? 0 : 1);
403     result = 37 * result + (isDisabled() ? 1 : 0);
404     result = 37 * result + getMaximum();
405     result = 37 * result + (maximumMsgDet == null? 0 : maximumMsgDet.hashCode());
406     return result;
407   }
408 
409   /**
410    * <p>Set the value to property <code>disabled</code>. Default value is false.</p>
411    * @param isDisabled <code>true</code> if it's disabled, <code>false</code> otherwise.
412    */   
413   public void setDisabled(boolean isDisabled)
414   {
415     _facesBean.setProperty(_DISABLED_KEY, Boolean.valueOf(isDisabled));
416   }
417 
418   /**
419     * Return whether it is disabled.
420     * @return true if it's disabled and false if it's enabled. 
421     */   
422   public boolean isDisabled()
423   {
424     Boolean disabled = (Boolean) _facesBean.getProperty(_DISABLED_KEY);
425     
426     return (disabled != null) ? disabled.booleanValue() : false;
427   }  
428 
429   /**
430    * The {@link FacesMessage} to be returned if byte length validation fails.
431    * @param context Faces context
432    * @param value   The value entered / set by the user on the component
433    * @return error message when the length exceeds the maximum byte length set.
434    */
435   protected FacesMessage getLengthValidationFailureMessage(
436     FacesContext context,
437     UIComponent component,
438     String value
439     )
440   {
441     Object label = ValidatorUtils.getComponentLabel(component);
442 
443     Object maxMesgDetail = _getRawMaximumMessageDetail();
444     String maximumBytes  = String.valueOf(getMaximum());
445 
446     Object[] params = { label, value, maximumBytes};
447 
448     FacesMessage msg = MessageFactory.getMessage(context,
449                                                  MAXIMUM_MESSAGE_ID,
450                                                  maxMesgDetail,
451                                                  params,
452                                                  component);
453     return msg;
454   }
455 
456   private Object _getRawMaximumMessageDetail()
457   {
458     return _facesBean.getRawProperty(_MAXIMUM_MESSAGE_DETAIL_KEY);
459   }
460 
461   private static final FacesBean.Type _TYPE = new FacesBean.Type();
462 
463   private static final PropertyKey _ENCODING_KEY =
464     _TYPE.registerKey("encoding", String.class, "iso-8859-1");
465 
466   private static final PropertyKey _MAXIMUM_KEY =
467     _TYPE.registerKey("maximum", int.class, 0);
468 
469   private static final PropertyKey  _MAXIMUM_MESSAGE_DETAIL_KEY =
470     _TYPE.registerKey("messageDetailMaximum", String.class);
471 
472   private static final PropertyKey  _HINT_MAXIMUM_KEY =
473     _TYPE.registerKey("hintMaximum", String.class);
474   
475   // Default is false
476   private static final PropertyKey _DISABLED_KEY =
477     _TYPE.registerKey("disabled", Boolean.class, Boolean.FALSE);
478 
479   private FacesBean _facesBean = ValidatorUtils.getFacesBean(_TYPE);
480 
481   private boolean _isTransient = false;
482 
483 
484   private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(
485     ByteLengthValidator.class);
486 }