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