blob: 175e6573d800ba941211186d3e422bad83aa21cc [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.bval;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import org.apache.bval.model.Features;
import org.apache.bval.model.MetaBean;
import org.apache.bval.model.MetaProperty;
import org.apache.bval.model.ValidationContext;
import org.apache.bval.model.ValidationListener;
import org.apache.bval.util.AccessStrategy;
import org.apache.bval.util.PropertyAccess;
import org.apache.bval.util.ValidationHelper;
// TODO: centralize treatMapsLikeBeans
/**
* Description: Top-Level API-class to validate objects or object-trees. You can
* invoke, extend or utilize this class if you need other ways to integrate
* validation in your application.
* <p/>
* This class supports cyclic object graphs by keeping track of validated
* instances in the validation context.<br/>
*/
public class BeanValidator<T extends ValidationListener> {
private final MetaBeanFinder metaBeanFinder;
/**
* Create a new BeanValidator instance. Convenience constructor. Use the
* global instance of MetaBeanManagerFactory.getFinder().
*/
public BeanValidator() {
this(MetaBeanManagerFactory.getFinder());
}
/**
* Create a new BeanValidator instance.
*
* @param metaBeanFinder
*/
public BeanValidator(MetaBeanFinder metaBeanFinder) {
this.metaBeanFinder = metaBeanFinder;
}
/**
* Convenience API. validate a root object with all related objects with its
* default metaBean definition.
*
* @param bean
* @return results - validation results found
*/
public T validate(Object bean) {
MetaBean metaBean = getMetaBeanFinder().findForClass(bean.getClass());
return validate(bean, metaBean);
}
/**
* Convenience API. validate a root object with all related objects
* according to the metaBean.
*
* @param bean
* - a single bean or a collection of beans (that share the same
* metaBean!)
* @param metaBean
* @return results - validation results found
*/
public T validate(Object bean, MetaBean metaBean) {
ValidationContext<T> context = createContext();
context.setBean(bean, metaBean);
ValidationHelper.validateContext(context, new BeanValidatorCallback(context), treatMapsLikeBeans);
return context.getListener();
}
/**
* Validate the method parameters based on @Validate annotations.
* Requirements: Parameter, that are to be validated must be annotated with @Validate
*
* @param method
* - a method
* @param parameters
* - the parameters suitable to the method
* @return a validation result or null when there was nothing to validate
* @see Validate
*/
public T validateCall(Method method, Object[] parameters) {
if (parameters.length > 0) {
// shortcut (for performance!)
Annotation[][] annotations = method.getParameterAnnotations();
ValidationContext<T> context = null;
for (int i = 0; i < parameters.length; i++) {
for (Annotation anno : annotations[i]) {
if (anno instanceof Validate) {
if (context == null)
context = createContext();
if (determineMetaBean((Validate) anno, parameters[i], context)) {
ValidationHelper.validateContext(context, new BeanValidatorCallback(context),
treatMapsLikeBeans);
break; // next parameter
}
}
}
}
return context != null ? context.getListener() : null;
}
return null;
}
/**
* Determine the metabean for the given object.
*
* @param <VL>
* @param validate
* @param parameter
* @param context
* @return true when validation should happen, false to skip it
*/
protected <VL extends ValidationListener> boolean determineMetaBean(Validate validate, Object parameter,
ValidationContext<VL> context) {
if (validate.value().length() == 0) {
if (parameter == null)
return false;
Class<?> beanClass;
if (parameter instanceof Collection<?>) { // do not validate empty
// collection
Collection<?> coll = ((Collection<?>) parameter);
if (coll.isEmpty())
return false;
beanClass = coll.iterator().next().getClass(); // get first
// object
} else if (parameter.getClass().isArray()) {
beanClass = parameter.getClass().getComponentType();
} else {
beanClass = parameter.getClass();
}
context.setBean(parameter, getMetaBeanFinder().findForClass(beanClass));
} else {
context.setBean(parameter, getMetaBeanFinder().findForId(validate.value()));
}
return true;
}
/**
* factory method - overwrite in subclasses
*
* @return ValidationListener of the proper type
*/
@SuppressWarnings("unchecked")
protected T createResults() {
return (T) new ValidationResults();
}
/**
* factory method - overwrite in subclasses
*
* @return ValidationContext parameterized with our listener type
*/
protected ValidationContext<T> createContext() {
return new BeanValidationContext<T>(createResults());
}
/**
* Convenience API. validate a single property.
*
* @param bean
* - the root object
* @param metaProperty
* - metadata for the property
* @return validation results
*/
public T validateProperty(Object bean, MetaProperty metaProperty) {
ValidationContext<T> context = createContext();
context.setBean(bean);
context.setMetaProperty(metaProperty);
ValidationHelper.validateProperty(context);
return context.getListener();
}
/**
* {@inheritDoc} internal validate a bean (=not a collection of beans) and
* its related beans
*/
protected <VL extends ValidationListener> void validateBeanNet(ValidationContext<VL> context) {
if (context.collectValidated()) {
ValidationHelper.validateBean(context);
for (MetaProperty prop : context.getMetaBean().getProperties()) {
validateRelatedBean(context, prop);
}
}
}
/**
* Validate a property of a graph.
*
* @param <VL>
* @param context
* @param prop
*/
protected <VL extends ValidationListener> void validateRelatedBean(ValidationContext<VL> context, MetaProperty prop) {
AccessStrategy[] access = prop.getFeature(Features.Property.REF_CASCADE);
if (access == null && prop.getMetaBean() != null) { // single property
// access strategy
// save old values from context
final Object bean = context.getBean();
final MetaBean mbean = context.getMetaBean();
// modify context state for relationship-target bean
context.moveDown(prop, new PropertyAccess(bean.getClass(), prop.getName()));
ValidationHelper.validateContext(context, new BeanValidatorCallback(context), treatMapsLikeBeans);
// restore old values in context
context.moveUp(bean, mbean);
} else if (access != null) { // different accesses to relation
// save old values from context
final Object bean = context.getBean();
final MetaBean mbean = context.getMetaBean();
for (AccessStrategy each : access) {
// modify context state for relationship-target bean
context.moveDown(prop, each);
ValidationHelper.validateContext(context, new BeanValidatorCallback(context), treatMapsLikeBeans);
// restore old values in context
context.moveUp(bean, mbean);
}
}
}
private boolean treatMapsLikeBeans = false;
public boolean isTreatMapsLikeBeans() {
return treatMapsLikeBeans;
}
public void setTreatMapsLikeBeans(boolean treatMapsLikeBeans) {
this.treatMapsLikeBeans = treatMapsLikeBeans;
}
/**
* Get the metabean finder associated with this validator.
*
* @return a MetaBeanFinder
* @see org.apache.bval.MetaBeanManagerFactory#getFinder()
*/
public MetaBeanFinder getMetaBeanFinder() {
return metaBeanFinder;
}
/**
* Dispatches a call from {@link #validate()} to
* {@link BeanValidator#validateBeanNet(ValidationContext)} with the current
* context set.
*/
private class BeanValidatorCallback implements ValidationHelper.ValidateCallback {
private final ValidationContext<?> context;
public BeanValidatorCallback(ValidationContext<?> context) {
this.context = context;
}
public void validate() {
validateBeanNet(context);
}
}
}