/*
* Redistribution and use of this software and associated documentation
* ("Software"), with or without modification, are permitted provided
* that the following conditions are met:
*
* 1. Redistributions of source code must retain copyright
* statements and notices. Redistributions must also contain a
* copy of this document.
*
* 2. Redistributions in binary form must reproduce the
* above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* 3. The name "Exolab" must not be used to endorse or promote
* products derived from this Software without prior written
* permission of Intalio, Inc. For written permission,
* please contact info@exolab.org.
*
* 4. Products derived from this Software may not be called "Exolab"
* nor may "Exolab" appear in their names without prior written
* permission of Intalio, Inc. Exolab is a registered
* trademark of Intalio, Inc.
*
* 5. Due credit should be given to the Exolab Project
* (http://www.exolab.org/).
*
* THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Copyright 2000-2004 (C) Intalio, Inc. All Rights Reserved.
*
* $Id: FieldValidator.java 7497 2008-03-14 10:48:28Z wguttmn $
*/
package org.exolab.castor.xml;
import java.lang.reflect.Array;
import java.util.Enumeration;
import java.util.List;
import java.util.Vector;
import org.exolab.castor.mapping.FieldHandler;
import org.exolab.castor.xml.location.XPathLocation;
/**
* Handles field validation.
*
* @author <a href="mailto:kvisco-at-intalio.com">Keith Visco</a>
* @version $Revision: 7497 $ $Date: 2004-10-08 22:58:55 -0600 (Fri, 08 Oct 2004) $
*/
public class FieldValidator extends Validator {
/** Default value for descriptor names. If a Descriptor XML name value is
* set to this, then no name has been assigned yet. */
private static final String ERROR_NAME = "-error-if-this-is-used-";
/** Default minimum occurrance. */
private static final int DEFAULT_MIN = 0;
/** Default maximum occurrance. */
private static final int DEFAULT_MAX = -1;
/** Minimum number of occurrences for this element to pass validation. */
private int _minOccurs = DEFAULT_MIN;
/** Maximum number of occurrences for this element to pass validation. */
private int _maxOccurs = DEFAULT_MAX;
/** The Field Descriptor describing the field we validate. */
private XMLFieldDescriptor _descriptor = null;
/** The actual type validator which is used to validate single instances of
* the field. */
private TypeValidator _validator = null;
/**
* Creates a default FieldValidator.
*/
public FieldValidator() {
super();
}
/**
* Creates a new FieldValidator using the given TypeValidator.
* @param validator the TypeValidator to delegate validation to
*/
public FieldValidator(final TypeValidator validator) {
super();
this._validator = validator;
}
/**
* Returns the mimimum number of occurances for a given object.
*
* @return The mimimum number of occurances for a given object. A zero value
* denotes no lower bound (ie. the object is optional).
*/
public int getMinOccurs() {
return _minOccurs;
}
/**
* Returns the maximum number of occurances for a given object.
*
* @return The maximum number of occurances for a given object. A negative
* value denotes no upper bound.
*/
public int getMaxOccurs() {
return _maxOccurs;
}
/**
* Returns the TypeValidator.
* @return the TypeValidator.
*/
public TypeValidator getTypeValidator() {
return _validator;
}
/**
* Returns true if a TypeValidator has been set.
* @return true if a TypeValidator has been set.
*/
public boolean hasTypeValidator() {
return _validator != null;
}
/**
* Sets the mimimum number of occurances for a given object. A zero, or
* negative value denotes no lower bound (i.e., the object is optional).
*
* @param minOccurs the minimum number of times an object must occur in
* order to be valid.
*/
public void setMinOccurs(final int minOccurs) {
this._minOccurs = (minOccurs < 0) ? 0 : minOccurs;
}
/**
* Sets the maximum number of occurances for a given object. A negative
* value denotes no upper bound.
*
* @param maxOccurs the maximum number of times an object may occur.
*/
public void setMaxOccurs(final int maxOccurs) {
this._maxOccurs = maxOccurs;
}
/**
* Sets the field descriptor to use for obtaining information about the
* field to validate, such as the field name, the field handler, etc.
*
* @param descriptor the field descriptor for the field to validate
*/
public void setDescriptor(final XMLFieldDescriptor descriptor) {
this._descriptor = descriptor;
}
public void setValidator(final TypeValidator validator) {
this._validator = validator;
}
/**
* Validates the given Object.
*
* @param object the Object that contains the field to validate
* @param context the ValidationContext
* @throws ValidationException if validation fails
*/
public void validate(final Object object, final ValidationContext context)
throws ValidationException {
if (_descriptor == null || object == null || context.isValidated(object)) {
return;
}
// Don't validate "transient" fields.
if (_descriptor.isTransient()) {
return;
}
FieldHandler handler = _descriptor.getHandler();
if (handler == null) {
return;
}
// Get the value of the field
Object value = handler.getValue(object);
if (value == null) {
if (!_descriptor.isRequired() || _descriptor.isNillable()) {
return;
}
// deal with lenient id/idref validation accordingly, skipping exception handling
// in this case
if (_descriptor.isRequired()
&& _descriptor.getSchemaType() != null
&& _descriptor.getSchemaType().equals("IDREF")
&& context.getInternalContext().getLenientIdValidation()) {
return;
}
StringBuffer buff = new StringBuffer();
buff.append("The field '" + _descriptor.getFieldName() + "' ");
if (!ERROR_NAME.equals(_descriptor.getXMLName())) {
buff.append("(whose xml name is '" + _descriptor.getXMLName() + "') ");
}
buff.append("is a required field of class '" + object.getClass().getName());
throw new ValidationException(buff.toString());
}
if (_descriptor.isReference()) {
if (_validator != null) {
_validator.validate(value, context);
}
return;
}
// Prevent endless loop! Have we seen this object yet?
if (context != null) {
if (context.isValidated(object)) {
return;
}
//-- mark object as processed
context.addValidated(object);
}
// We are now ready to do actual validation
Class type = value.getClass();
int size = 1;
long occurence = -1;
try {
if (type.isArray()) {
// We don't validate Byte array types
if (type.getComponentType() != Byte.TYPE) {
size = Array.getLength(value);
if (_validator != null) {
for (int i = 0; i < size; i++) {
occurence = i + 1;
_validator.validate(Array.get(value, i), context);
}
} else {
for (int i = 0; i < size; i++) {
super.validate(Array.get(value, i), context);
}
}
}
} else if (value instanceof Enumeration) {
// <NOTE>
// The following code should be changed to use CollectionHandler
// </NOTE>
size = 0;
for (Enumeration enumeration = (Enumeration) value;
enumeration.hasMoreElements(); ) {
++size;
validateInstance(context, enumeration.nextElement());
}
} else if (value instanceof Vector) {
Vector vector = (Vector) value;
size = vector.size();
for (int i = 0; i < size; i++) {
occurence = i + 1;
validateInstance(context, vector.elementAt(i));
}
} else if (value instanceof List) {
List list = (List) value;
size = list.size();
for (int i = 0; i < size; i++) {
occurence = i + 1;
validateInstance(context, list.get(i));
}
} else {
validateInstance(context, value);
}
} catch (ValidationException vx) {
//-- add additional validation information
String err = "The following exception occured while validating field: "
+ _descriptor.getFieldName() + " of class: "
+ object.getClass().getName();
ValidationException validationException = new ValidationException(err, vx);
addLocationInformation(_descriptor, validationException, occurence);
throw validationException;
}
// Check sizes of collection
// Check minimum.
// If any items exist (size != 0) or the descriptor is marked as
// required then we need to report the error. Otherwise size == 0 and
// field is not required, so no error.
if (size < _minOccurs && (size != 0 || _descriptor.isRequired())) {
StringBuffer buff = new StringBuffer();
buff.append("A minimum of " + _minOccurs + " " + _descriptor.getFieldName()
+ " object(s) ");
if (!ERROR_NAME.equals(_descriptor.getXMLName())) {
buff.append("(whose xml name is '" + _descriptor.getXMLName() + "') ");
}
buff.append("are required for class: " + object.getClass().getName());
throw new ValidationException(buff.toString());
}
// Check maximum.
if (_maxOccurs >= 0 && size > _maxOccurs) {
StringBuffer buff = new StringBuffer();
buff.append("A maximum of " + _maxOccurs + " " + _descriptor.getFieldName()
+ " object(s) ");
if (!ERROR_NAME.equals(_descriptor.getXMLName())) {
buff.append("(whose xml name is '" + _descriptor.getXMLName() + "') ");
}
buff.append("are allowed for class: " + object.getClass().getName() + ".");
throw new ValidationException(buff.toString());
}
if (context != null) {
context.removeValidated(object);
}
}
/**
* Validate an individual instance.
* @param context the validation context.
* @param value The instance to validate.
* @throws ValidationException if validation fails
*/
private void validateInstance(final ValidationContext context, final Object value)
throws ValidationException {
if (_validator != null) {
_validator.validate(value, context);
} else {
super.validate(value, context);
}
}
/**
* Adds location information to the {@link ValidationException} instance.
* @param fieldDescriptor The {@link XMLFieldDescriptor} instance whose has been responsible for
* generating the error.
* @param e The {@link ValidationException} to enrich.
* @param occurence If greater than 0, denotes the position of the invalid collection value.
*/
private void addLocationInformation(final XMLFieldDescriptor fieldDescriptor,
final ValidationException e, final long occurence) {
XPathLocation loc = (XPathLocation) e.getLocation();
if (loc == null) {
loc = new XPathLocation();
e.setLocation(loc);
String xmlName = fieldDescriptor.getXMLName();
if (occurence > 0) {
xmlName += "[" + occurence + "]";
}
if (fieldDescriptor.getNodeType() == NodeType.Attribute) {
loc.addAttribute(xmlName);
} else {
loc.addChild(xmlName);
}
}
}
}