001
002 /*
003 * Copyright (C) 2011 Archie L. Cobbs. All rights reserved.
004 *
005 * $Id: ValidationContext.java 201 2011-12-30 18:40:35Z archie.cobbs $
006 */
007
008 package org.dellroad.stuff.validation;
009
010 import java.util.Set;
011
012 import javax.validation.ConstraintViolation;
013 import javax.validation.Validation;
014 import javax.validation.Validator;
015
016 /**
017 * Provides additional context for {@link javax.validation.ConstraintValidator} implementations.
018 *
019 * <p>
020 * {@link ValidationContext} gives {@link javax.validation.ConstraintValidator} implementations access to the root object
021 * being validated. This breaks the usual principle of locality for validation (i.e., that validation of a specific bean
022 * proceeds unaware of that bean's parents) but it can make custom validators more convenient to implement.
023 * Subclasses are encouraged to provide additional application-specific information.
024 *
025 * <p>
026 * Validation must be performed via {@link #validate(Validator) validate()} for this class to work.
027 */
028 public class ValidationContext<T> {
029
030 static final ThreadLocal<ValidationContext> CURRENT = new ThreadLocal<ValidationContext>();
031
032 private final T root;
033
034 /**
035 * Construct a new validation context configured to validate the given root object.
036 *
037 * @param root root object to be validated
038 * @throws IllegalArgumentException if {@code root} is null
039 */
040 public ValidationContext(T root) {
041 if (root == null)
042 throw new IllegalArgumentException("null root");
043 this.root = root;
044 }
045
046 /**
047 * Get the root object associated with this instance.
048 */
049 public final T getRoot() {
050 return this.root;
051 }
052
053 /**
054 * Validate this instance's root object. This is a convenience method, equivalent to:
055 * <blockquote>
056 * <code>{@link #validate(Validator) validate}(Validation.buildDefaultValidatorFactory().getValidator())</code>
057 * <blockquote>
058 *
059 * @throws IllegalStateException if this method is invoked re-entrantly
060 */
061 public Set<ConstraintViolation<T>> validate() {
062 return this.validate(Validation.buildDefaultValidatorFactory().getValidator());
063 }
064
065 /**
066 * Validate this instance's root object using the given {@link Validator}, making the context
067 * available to the current thread during the validation process via {@link #getCurrentContext}.
068 *
069 * @throws IllegalStateException if this method is invoked re-entrantly
070 */
071 public Set<ConstraintViolation<T>> validate(Validator validator) {
072
073 // Sanity check
074 if (ValidationContext.CURRENT.get() != null)
075 throw new IllegalStateException("re-entrant invocation is not allowed");
076
077 // Set context, then validate
078 ValidationContext.CURRENT.set(this);
079 try {
080 return validator.validate(this.root);
081 } finally {
082 ValidationContext.CURRENT.remove();
083 }
084 }
085
086 /**
087 * Get the {@link ValidationContext} associated with the current thread, cast to the desired type.
088 * This method is only valid during invocations of {@link #validate(Validator) validate()}.
089 *
090 * @param type required type
091 * @return current {@link ValidationContext}
092 * @throws IllegalStateException if {@link #validate(Validator) validate()} is not currently executing
093 * @throws ClassCastException if the current {@link ValidationContext} is not of type {@code type}
094 */
095 public static <T extends ValidationContext<?>> T getCurrentContext(Class<T> type) {
096 ValidationContext<?> context = ValidationContext.CURRENT.get();
097 if (context == null)
098 throw new IllegalStateException("current thread is not executing validate()");
099 return type.cast(context);
100 }
101
102 /**
103 * Convenience method to get the root object being validated by the current thread.
104 * This method is only valid during invocations of {@link #validate(Validator) validate()}.
105 * Subclasses may want to override to narrow the return type.
106 *
107 * @return current validation root object
108 * @throws IllegalStateException if {@link #validate(Validator) validate()} is not currently executing
109 */
110 public static Object getCurrentRoot() {
111 return ValidationContext.getCurrentContext(ValidationContext.class).getRoot();
112 }
113 }
114