| /* |
| * 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); |
| } |
| |
| } |
| |
| } |