View Javadoc
1   /*
2    * Redistribution and use of this software and associated documentation ("Software"), with or
3    * without modification, are permitted provided that the following conditions are met:
4    *
5    * 1. Redistributions of source code must retain copyright statements and notices. Redistributions
6    * must also contain a copy of this document.
7    *
8    * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
9    * conditions and the following disclaimer in the documentation and/or other materials provided with
10   * the distribution.
11   *
12   * 3. The name "Exolab" must not be used to endorse or promote products derived from this Software
13   * without prior written permission of Intalio, Inc. For written permission, please contact
14   * info@exolab.org.
15   *
16   * 4. Products derived from this Software may not be called "Exolab" nor may "Exolab" appear in
17   * their names without prior written permission of Intalio, Inc. Exolab is a registered trademark of
18   * Intalio, Inc.
19   *
20   * 5. Due credit should be given to the Exolab Project (http://www.exolab.org/).
21   *
22   * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR
23   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
24   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTALIO, INC. OR ITS
25   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
29   * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30   *
31   * Copyright 2000-2004 (C) Intalio, Inc. All Rights Reserved.
32   *
33   * $Id$
34   */
35  package org.exolab.castor.xml;
36  
37  import java.lang.reflect.Array;
38  import java.text.MessageFormat;
39  import java.util.Enumeration;
40  import java.util.List;
41  import java.util.Vector;
42  
43  import org.exolab.castor.mapping.FieldHandler;
44  import org.exolab.castor.xml.location.XPathLocation;
45  
46  /**
47   * Handles field validation.
48   *
49   * @author <a href="mailto:kvisco-at-intalio.com">Keith Visco</a>
50   * @version $Revision$ $Date: 2004-10-08 22:58:55 -0600 (Fri, 08 Oct 2004) $
51   */
52  public class FieldValidator extends Validator {
53  
54    /**
55     * Default value for descriptor names. If a Descriptor XML name value is set to this, then no name
56     * has been assigned yet.
57     */
58    private static final String ERROR_NAME = "-error-if-this-is-used-";
59    /** Default minimum occurrance. */
60    private static final int DEFAULT_MIN = 0;
61    /** Default maximum occurrance. */
62    private static final int DEFAULT_MAX = -1;
63  
64    /** Minimum number of occurrences for this element to pass validation. */
65    private int _minOccurs = DEFAULT_MIN;
66    /** Maximum number of occurrences for this element to pass validation. */
67    private int _maxOccurs = DEFAULT_MAX;
68    /** The Field Descriptor describing the field we validate. */
69    private XMLFieldDescriptor _descriptor = null;
70    /**
71     * The actual type validator which is used to validate single instances of the field.
72     */
73    private TypeValidator _validator = null;
74  
75    /**
76     * Creates a default FieldValidator.
77     */
78    public FieldValidator() {
79      super();
80    }
81  
82    /**
83     * Creates a new FieldValidator using the given TypeValidator.
84     * 
85     * @param validator the TypeValidator to delegate validation to
86     */
87    public FieldValidator(final TypeValidator validator) {
88      super();
89      this._validator = validator;
90    }
91  
92    /**
93     * Returns the mimimum number of occurances for a given object.
94     *
95     * @return The mimimum number of occurances for a given object. A zero value denotes no lower
96     *         bound (ie. the object is optional).
97     */
98    public int getMinOccurs() {
99      return _minOccurs;
100   }
101 
102   /**
103    * Returns the maximum number of occurances for a given object.
104    *
105    * @return The maximum number of occurances for a given object. A negative value denotes no upper
106    *         bound.
107    */
108   public int getMaxOccurs() {
109     return _maxOccurs;
110   }
111 
112   /**
113    * Returns the TypeValidator.
114    * 
115    * @return the TypeValidator.
116    */
117   public TypeValidator getTypeValidator() {
118     return _validator;
119   }
120 
121   /**
122    * Returns true if a TypeValidator has been set.
123    * 
124    * @return true if a TypeValidator has been set.
125    */
126   public boolean hasTypeValidator() {
127     return _validator != null;
128   }
129 
130   /**
131    * Sets the mimimum number of occurances for a given object. A zero, or negative value denotes no
132    * lower bound (i.e., the object is optional).
133    *
134    * @param minOccurs the minimum number of times an object must occur in order to be valid.
135    */
136   public void setMinOccurs(final int minOccurs) {
137     this._minOccurs = (minOccurs < 0) ? 0 : minOccurs;
138   }
139 
140   /**
141    * Sets the maximum number of occurances for a given object. A negative value denotes no upper
142    * bound.
143    *
144    * @param maxOccurs the maximum number of times an object may occur.
145    */
146   public void setMaxOccurs(final int maxOccurs) {
147     this._maxOccurs = maxOccurs;
148   }
149 
150   /**
151    * Sets the field descriptor to use for obtaining information about the field to validate, such as
152    * the field name, the field handler, etc.
153    *
154    * @param descriptor the field descriptor for the field to validate
155    */
156   public void setDescriptor(final XMLFieldDescriptor descriptor) {
157     this._descriptor = descriptor;
158   }
159 
160   public void setValidator(final TypeValidator validator) {
161     this._validator = validator;
162   }
163 
164   /**
165    * Validates the given Object.
166    *
167    * @param object the Object that contains the field to validate
168    * @param context the ValidationContext
169    * @throws ValidationException if validation fails
170    */
171   public void validate(final Object object, final ValidationContext context)
172       throws ValidationException {
173     if (_descriptor == null || object == null || context.isValidated(object)) {
174       return;
175     }
176 
177     // Don't validate "transient" fields.
178     if (_descriptor.isTransient()) {
179       return;
180     }
181 
182     FieldHandler handler = _descriptor.getHandler();
183     if (handler == null) {
184       return;
185     }
186 
187     // Get the value of the field
188     Object value = handler.getValue(object);
189     if (value == null) {
190       if (!_descriptor.isRequired() || _descriptor.isNillable()) {
191         return;
192       }
193       // deal with lenient id/idref validation accordingly, skipping exception handling
194       // in this case
195       if (_descriptor.isRequired() && _descriptor.getSchemaType() != null
196           && _descriptor.getSchemaType().equals("IDREF")
197           && context.getInternalContext().getLenientIdValidation()) {
198         return;
199       }
200       StringBuilder buff = new StringBuilder();
201       if (!ERROR_NAME.equals(_descriptor.getXMLName())) {
202         buff.append(MessageFormat.format(
203             resourceBundle.getString("validatorField.error.required.field.whose"),
204             new Object[] {_descriptor.getFieldName(), object.getClass().getName(),
205                 _descriptor.getXMLName()}));
206       } else {
207         buff.append(
208             MessageFormat.format(resourceBundle.getString("validatorField.error.required.field"),
209                 new Object[] {_descriptor.getFieldName(), object.getClass().getName()}));
210       }
211 
212       throw new ValidationException(buff.toString());
213     }
214 
215     if (_descriptor.isReference()) {
216       if (_validator != null) {
217         _validator.validate(value, context);
218       }
219       return;
220     }
221 
222     // Prevent endless loop! Have we seen this object yet?
223     if (context != null) {
224       if (context.isValidated(object)) {
225         return;
226       }
227       // -- mark object as processed
228       context.addValidated(object);
229     }
230 
231     // We are now ready to do actual validation
232 
233     Class type = value.getClass();
234     int size = 1;
235     long occurence = -1;
236 
237     try {
238       if (type.isArray()) {
239         // We don't validate Byte array types
240         if (type.getComponentType() != Byte.TYPE) {
241           size = Array.getLength(value);
242           if (_validator != null) {
243             for (int i = 0; i < size; i++) {
244               occurence = i + 1;
245               _validator.validate(Array.get(value, i), context);
246             }
247           } else {
248             for (int i = 0; i < size; i++) {
249               super.validate(Array.get(value, i), context);
250             }
251           }
252         }
253       } else if (value instanceof Enumeration) {
254         // <NOTE>
255         // The following code should be changed to use CollectionHandler
256         // </NOTE>
257         size = 0;
258         for (Enumeration enumeration = (Enumeration) value; enumeration.hasMoreElements();) {
259           ++size;
260           validateInstance(context, enumeration.nextElement());
261         }
262       } else if (value instanceof Vector) {
263         Vector vector = (Vector) value;
264         size = vector.size();
265         for (int i = 0; i < size; i++) {
266           occurence = i + 1;
267           validateInstance(context, vector.elementAt(i));
268         }
269       } else if (value instanceof List) {
270         List list = (List) value;
271         size = list.size();
272         for (int i = 0; i < size; i++) {
273           occurence = i + 1;
274           validateInstance(context, list.get(i));
275         }
276       } else {
277         validateInstance(context, value);
278       }
279     } catch (ValidationException vx) {
280       // -- add additional validation information
281       String err = MessageFormat.format(resourceBundle.getString("validatorField.error.exception"),
282           new Object[] {_descriptor.getFieldName(), object.getClass().getName()});
283       ValidationException validationException = new ValidationException(err, vx);
284       addLocationInformation(_descriptor, validationException, occurence);
285       throw validationException;
286     }
287 
288     // Check sizes of collection
289 
290     // Check minimum.
291     // If any items exist (size != 0) or the descriptor is marked as
292     // required then we need to report the error. Otherwise size == 0 and
293     // field is not required, so no error.
294     if (size < _minOccurs && (size != 0 || _descriptor.isRequired())) {
295       StringBuilder buff = new StringBuilder();
296       if (!ERROR_NAME.equals(_descriptor.getXMLName())) {
297         buff.append(MessageFormat.format(
298             resourceBundle.getString("validatorField.error.exception.min.occurs.whose"),
299             new Object[] {_minOccurs, _descriptor.getFieldName(), object.getClass().getName(),
300                 _descriptor.getXMLName()}));
301       } else {
302         buff.append(MessageFormat.format(
303             resourceBundle.getString("validatorField.error.exception.min.occurs"),
304             new Object[] {_minOccurs, _descriptor.getFieldName(), object.getClass().getName()}));
305       }
306       throw new ValidationException(buff.toString());
307     }
308 
309     // Check maximum.
310     if (_maxOccurs >= 0 && size > _maxOccurs) {
311       StringBuilder buff = new StringBuilder();
312       if (!ERROR_NAME.equals(_descriptor.getXMLName())) {
313         buff.append(MessageFormat.format(
314             resourceBundle.getString("validatorField.error.exception.max.occurs.whose"),
315             new Object[] {_maxOccurs, _descriptor.getFieldName(), object.getClass().getName(),
316                 _descriptor.getXMLName()}));
317       } else {
318         buff.append(MessageFormat.format(
319             resourceBundle.getString("validatorField.error.exception.max.occurs"),
320             new Object[] {_maxOccurs, _descriptor.getFieldName(), object.getClass().getName()}));
321       }
322 
323       throw new ValidationException(buff.toString());
324     }
325 
326     if (context != null) {
327       context.removeValidated(object);
328     }
329   }
330 
331   /**
332    * Validate an individual instance.
333    * 
334    * @param context the validation context.
335    * @param value The instance to validate.
336    * @throws ValidationException if validation fails
337    */
338   private void validateInstance(final ValidationContext context, final Object value)
339       throws ValidationException {
340     if (_validator != null) {
341       _validator.validate(value, context);
342     } else {
343       super.validate(value, context);
344     }
345   }
346 
347   /**
348    * Adds location information to the {@link ValidationException} instance.
349    * 
350    * @param fieldDescriptor The {@link XMLFieldDescriptor} instance whose has been responsible for
351    *        generating the error.
352    * @param e The {@link ValidationException} to enrich.
353    * @param occurence If greater than 0, denotes the position of the invalid collection value.
354    */
355   private void addLocationInformation(final XMLFieldDescriptor fieldDescriptor,
356       final ValidationException e, final long occurence) {
357     XPathLocation loc = (XPathLocation) e.getLocation();
358     if (loc == null) {
359       loc = new XPathLocation();
360       e.setLocation(loc);
361       String xmlName = fieldDescriptor.getXMLName();
362       if (occurence > 0) {
363         xmlName += "[" + occurence + "]";
364       }
365       if (fieldDescriptor.getNodeType() == NodeType.Attribute) {
366         loc.addAttribute(xmlName);
367       } else {
368         loc.addChild(xmlName);
369       }
370     }
371   }
372 
373 }