blob: b9b8373fd08742125d64d8a0948c5ed6e7293faf [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 org.apache.bval.model.*;
import org.apache.bval.util.AccessStrategy;
import org.apache.bval.util.PropertyAccess;
import java.util.IdentityHashMap;
import java.util.Map;
/**
* Description: Context during validation to help the {@link org.apache.bval.model.Validation}
* and the {@link BeanValidator} do their jobs.
* Used to bundle {@link BeanValidationContext} and {@link ValidationListener}
* together <br/>
* <b>This class is NOT thread-safe: a new instance will be created for each
* validation
* processing per thread.<br/></b>
*/
public class BeanValidationContext<T extends ValidationListener>
implements ValidationContext<T> {
/** represent an unknown propertyValue. */
private static final Object UNKNOWN = new Object() {
public String toString() {
return "unknown property value";
};
};
/** metainfo of current object. */
private MetaBean metaBean;
/** current object. */
private Object bean;
/** metainfo of current property. */
private MetaProperty metaProperty;
/**
* cached value of current property.
* Cached because of potential redundant access for different Validations
*/
private Object propertyValue = UNKNOWN;
/** access strategy used for previous access */
private AccessStrategy access;
/** set of objects already validated to avoid endless loops. */
@SuppressWarnings({ "rawtypes" })
protected Map validatedObjects;
/**
* true when value is fixed, so that it will NOT be dynamically
* determined from the annotated element or the metaProperty.
* <b><br>Note: When value is UNKNOWN, it will be determined THE FIRST TIME
* IT IS ACCESSED.</b>
*/
private boolean fixed;
/** listener notified of validation constraint violations. */
private T listener;
/**
* Create a new BeanValidationContext instance.
* @param listener
*/
@SuppressWarnings({ "rawtypes" })
public BeanValidationContext(T listener) {
this(listener, new IdentityHashMap());
}
/**
* Create a new BeanValidationContext instance.
* @param listener
* @param validatedMap
*/
@SuppressWarnings({ "rawtypes" })
protected BeanValidationContext(T listener, Map validatedMap) {
this.listener = listener;
this.validatedObjects = validatedMap;
}
/**
* {@inheritDoc}
*/
public T getListener() {
return listener;
}
/**
* Set the listener.
* @param listener T
*/
public void setListener(T listener) {
this.listener = listener;
}
/**
* {@inheritDoc}
* Here, state equates to a given bean reference.
*/
@SuppressWarnings("unchecked")
public boolean collectValidated() {
return validatedObjects.put(getBean(), Boolean.TRUE) == null;
}
/**
* Learn whether a particular object has been validated.
* @param object
* @return true when the object has already been validated in this context
*/
public boolean isValidated(Object object) {
return validatedObjects.containsKey(object);
}
/**
* Clear map of validated objects (invoke when you want to 'reuse' the
* context for different validations)
*/
public void resetValidated() {
validatedObjects.clear();
}
/**
* {@inheritDoc}
*/
public void setBean(Object aBean, MetaBean aMetaBean) {
bean = aBean;
metaBean = aMetaBean;
metaProperty = null;
unknownValue();
}
/**
* Get the cached value or access it somehow (via field or method)<br>
* <b>you should prefer getPropertyValue(AccessStrategy) instead of this method</b>
*
* @return the current value of the property accessed by reflection
* @throws IllegalArgumentException - error accessing attribute (config error, reflection problem)
* @throws IllegalStateException - when no property is currently set in the context (application logic bug)
*/
public Object getPropertyValue() {
if (access == null) { // undefined access strategy
return getPropertyValue(
new PropertyAccess(bean.getClass(), metaProperty.getName()));
} else {
return getPropertyValue(access);
}
}
/**
* {@inheritDoc}
* Caches retrieved value.
*/
public Object getPropertyValue(AccessStrategy access)
throws IllegalArgumentException, IllegalStateException {
if (propertyValue == UNKNOWN || (this.access != access && !fixed)) {
propertyValue = access.get(bean);
this.access = access;
}
return propertyValue;
}
/**
* Convenience method to access metaProperty.name
*
* @return null or the name of the current property
*/
public String getPropertyName() {
return metaProperty == null ? null : metaProperty.getName();
}
/**
* Set the current property value.
* @param propertyValue
*/
public void setPropertyValue(Object propertyValue) {
this.propertyValue = propertyValue;
}
/**
* Set the property value, fixed.
* @param value
*/
public void setFixedValue(Object value) {
setPropertyValue(value);
setFixed(true);
}
/**
* Learn whether the current property value is "fixed."
* @return boolean
*/
public boolean isFixed() {
return fixed;
}
/**
* Potentially declare the current property value as being "fixed."
* If <code>true</code>, the context will reuse any not-<code>UNKNOWN</code>
* propertyValue regardless of the {@link AccessStrategy} by which it is requested.
* @param fixed
*/
public void setFixed(boolean fixed) {
this.fixed = fixed;
}
/**
* Depending on whether we have a metaProperty or not,
* this returns the metaProperty or otherwise the metaBean.
* This is used to have a simple way to request features
* in the Validation for both bean- and property-level validations.
*
* @return something that is capable to deliver features
*/
public FeaturesCapable getMeta() {
return (metaProperty == null) ? metaBean : metaProperty;
}
/**
* Drop cached value, marking the internal cachedValue as <code>UNKNOWN</code>.
* This forces the BeanValidationContext to recompute the value
* the next time it is accessed.
* Use this method inside tests or when the propertyValue has been
* changed during validation.
*/
public void unknownValue() {
propertyValue = UNKNOWN;
access = null;
fixed = false;
}
/**
* {@inheritDoc}
*/
public MetaBean getMetaBean() {
return metaBean;
}
/**
* {@inheritDoc}
*/
public Object getBean() {
return bean;
}
/**
* {@inheritDoc}
*/
public MetaProperty getMetaProperty() {
return metaProperty;
}
/**
* {@inheritDoc}
*/
public void setMetaBean(MetaBean metaBean) {
this.metaBean = metaBean;
}
/**
* {@inheritDoc}
*/
public void setBean(Object bean) {
this.bean = bean;
unknownValue();
}
/**
* {@inheritDoc}
*/
public void setMetaProperty(MetaProperty metaProperty) {
this.metaProperty = metaProperty;
unknownValue();
}
/**
* {@inheritDoc}
*/
public String toString() {
return "BeanValidationContext{ bean=" + bean + ", metaProperty=" + metaProperty +
", propertyValue=" + propertyValue + '}';
}
/**
* {@inheritDoc}
*/
public void moveDown(MetaProperty prop, AccessStrategy access) {
setBean(getPropertyValue(access), prop.getMetaBean());
}
/**
* {@inheritDoc}
*/
public void moveUp(Object bean, MetaBean aMetaBean) {
setBean(bean, aMetaBean); // reset context state
}
/**
* {@inheritDoc}
*/
public void setCurrentIndex(Integer index) {
// do nothing
}
/**
* {@inheritDoc}
*/
public void setCurrentKey(Object key) {
// do nothing
}
/**
* {@inheritDoc}
*/
public AccessStrategy getAccess() {
return this.access;
}
}