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