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