/*
* Beanfabrics Framework Copyright (C) by Michael Karneim, beanfabrics.org
* Use is subject to license terms. See license.txt.
*/
// TODO javadoc - remove this comment only when the class and all non-public
// methods and fields are documented
package org.beanfabrics.support;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import org.beanfabrics.Path;
import org.beanfabrics.PathObservation;
import org.beanfabrics.log.Logger;
import org.beanfabrics.log.LoggerFactory;
import org.beanfabrics.model.PresentationModel;
import org.beanfabrics.util.ReflectionUtil;
import org.beanfabrics.util.ResourceBundleFactory;
import org.beanfabrics.validation.ValidationRule;
import org.beanfabrics.validation.ValidationState;
/**
* This class supports the processing of {@link Validation} annotations for
* {@link PresentationModel} classes. Annotated methods are constructed into
* {@link ValidationRule}s and included into the validation process of the
* referenced model.
*
* @see <a href="http://www.beanfabrics.org/wiki/index.php/Validation_Tutorial">
* beanfabrics tutorial on the validation framework< /a>
* @author Michael Karneim
*/
public class ValidationSupport implements Support {
private static Logger LOG = LoggerFactory.getLogger(ValidationSupport.class);
private static final String KEY_MESSAGE_VALIDATION_FAILED = "message.validationFailed";
public static ValidationSupport get(PresentationModel model) {
ValidationSupport support = model.getSupportMap().get(ValidationSupport.class);
if (support == null) {
support = new ValidationSupport(model);
model.getSupportMap().put(ValidationSupport.class, support);
}
return support;
}
private final ResourceBundle resourceBundle = ResourceBundleFactory.getBundle(ValidationSupport.class);
private PresentationModel model;
private Map<Method, ValidationMethodSupport> map = new HashMap<Method, ValidationMethodSupport>();
private ValidationSupport(PresentationModel model) {
if (model == null) {
throw new IllegalArgumentException("model==null");
}
this.model = model;
}
public void setup(Method method) {
if (map.containsKey(method) == false) {
ValidationMethodSupport support = support(model, method, resourceBundle);
map.put(method, support);
}
}
private static ValidationMethodSupport support(PresentationModel pModel, Method m, ResourceBundle resourceBundle) {
return new ValidationMethodSupport(pModel, m, resourceBundle);
}
private static class ValidationMethodSupport {
private final ResourceBundle resourceBundle;
private final PresentationModel owner;
private final List<PathObservation> obervations = new LinkedList<PathObservation>();
private final Method annotatedMethod;
private final ValidationRule rule;
private ValidationMethodSupport(PresentationModel owner, Method annotatedMethod, ResourceBundle bundle) {
this.resourceBundle = bundle;
Validation anno = annotatedMethod.getAnnotation(Validation.class);
String message = anno.message();
boolean validWhen = anno.validWhen();
if (annotatedMethod.getParameterTypes() != null && annotatedMethod.getParameterTypes().length > 0) {
throw new IllegalArgumentException("method '" + annotatedMethod.getName() + "' must not declare any parameter when annotated with @Validation");
}
if (Boolean.TYPE.isAssignableFrom(annotatedMethod.getReturnType())) {
if (message == null || message.length() == 0) {
//throw new IllegalArgumentException("@Validation on boolean method '"+annotatedMethod.getName()+"' must define the message attribute");
message = getDefaultMessage();
}
rule = new BooleanMethodValidationRule(validWhen, message);
} else if (ValidationState.class.isAssignableFrom(annotatedMethod.getReturnType())) {
if (message != null && message.length() > 0) {
throw new IllegalArgumentException("@Validation on method '" + annotatedMethod.getName() + "' must NOT define the message attribute");
}
rule = new ValidationStateMethodValidationRule();
} else {
throw new IllegalArgumentException("the return type of method '" + annotatedMethod.getName() + "' must either be boolean or a ValidationState");
}
this.owner = owner;
this.annotatedMethod = annotatedMethod;
for (int i = 0; i < anno.path().length; ++i) {
Path path = Path.parse(anno.path()[i]);
LOG.debug("Observing " + owner + " at " + path + ".");
this.obervations.add(new ValidationTargetObservation(owner, path));
}
}
private String getDefaultMessage() {
String message = resourceBundle.getString(KEY_MESSAGE_VALIDATION_FAILED);
return message;
}
private class ValidationTargetObservation extends PathObservation {
private PresentationModel currentTarget = null;
public ValidationTargetObservation(PresentationModel root, Path path) {
super(root, path);
this.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
addValidationRuleToTarget();
}
});
this.addValidationRuleToTarget();
}
public void addValidationRuleToTarget() {
PresentationModel newTarget = this.getTarget();
if (newTarget == this.currentTarget) {
return; // nothing to do
}
if (this.currentTarget != null) {
this.currentTarget.getValidator().remove(ValidationMethodSupport.this.rule);
}
this.currentTarget = newTarget;
if (this.currentTarget != null) {
LOG.debug("Adding validation rule to " + newTarget + " in " + getRootNode() + " at " + getPath() + ".");
this.currentTarget.getValidator().add(ValidationMethodSupport.this.rule);
}
}
}
private class BooleanMethodValidationRule implements ValidationRule {
private final boolean validWhen;
private final String message;
public BooleanMethodValidationRule(boolean validWhen, String message) {
this.validWhen = validWhen;
this.message = message;
}
public ValidationState validate() {
boolean isValid = callAnnotatedMethod();
if (isValid == validWhen) {
return null;
} else {
return ValidationState.create(message);
}
}
private boolean callAnnotatedMethod() {
boolean result;
try {
result = (Boolean)ReflectionUtil.invokeMethod(owner, annotatedMethod, (Object[])null);
} catch (IllegalArgumentException e) {
throw new UndeclaredThrowableException(e);
} catch (IllegalAccessException e) {
throw new UndeclaredThrowableException(e);
} catch (InvocationTargetException e) {
throw new UndeclaredThrowableException(e);
}
return result;
}
}
private class ValidationStateMethodValidationRule implements ValidationRule {
public ValidationState validate() {
ValidationState result = callAnnotatedMethod();
return result;
}
private ValidationState callAnnotatedMethod() {
ValidationState result;
try {
result = (ValidationState)ReflectionUtil.invokeMethod(owner, annotatedMethod, (Object[])null);
} catch (IllegalArgumentException e) {
throw new UndeclaredThrowableException(e);
} catch (IllegalAccessException e) {
throw new UndeclaredThrowableException(e);
} catch (InvocationTargetException e) {
throw new UndeclaredThrowableException(e);
}
return result;
}
}
}
}