All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hibernate.validator.internal.metadata.core.ConstraintHelper Maven / Gradle / Ivy

/*
* JBoss, Home of Professional Open Source
* Copyright 2010, Red Hat, Inc. and/or its affiliates, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* Licensed 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.hibernate.validator.internal.metadata.core;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import javax.validation.Constraint;
import javax.validation.ConstraintTarget;
import javax.validation.ConstraintValidator;
import javax.validation.ValidationException;
import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Digits;
import javax.validation.constraints.Future;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import javax.validation.constraintvalidation.SupportedValidationTarget;
import javax.validation.constraintvalidation.ValidationTarget;

import org.hibernate.validator.constraints.ConstraintComposition;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.ModCheck;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.SafeHtml;
import org.hibernate.validator.constraints.ScriptAssert;
import org.hibernate.validator.constraints.URL;
import org.hibernate.validator.internal.constraintvalidators.AssertFalseValidator;
import org.hibernate.validator.internal.constraintvalidators.AssertTrueValidator;
import org.hibernate.validator.internal.constraintvalidators.DecimalMaxValidatorForCharSequence;
import org.hibernate.validator.internal.constraintvalidators.DecimalMaxValidatorForNumber;
import org.hibernate.validator.internal.constraintvalidators.DecimalMinValidatorForCharSequence;
import org.hibernate.validator.internal.constraintvalidators.DecimalMinValidatorForNumber;
import org.hibernate.validator.internal.constraintvalidators.DigitsValidatorForCharSequence;
import org.hibernate.validator.internal.constraintvalidators.DigitsValidatorForNumber;
import org.hibernate.validator.internal.constraintvalidators.EmailValidator;
import org.hibernate.validator.internal.constraintvalidators.FutureValidatorForCalendar;
import org.hibernate.validator.internal.constraintvalidators.FutureValidatorForDate;
import org.hibernate.validator.internal.constraintvalidators.FutureValidatorForReadableInstant;
import org.hibernate.validator.internal.constraintvalidators.FutureValidatorForReadablePartial;
import org.hibernate.validator.internal.constraintvalidators.LengthValidator;
import org.hibernate.validator.internal.constraintvalidators.MaxValidatorForCharSequence;
import org.hibernate.validator.internal.constraintvalidators.MaxValidatorForNumber;
import org.hibernate.validator.internal.constraintvalidators.MinValidatorForCharSequence;
import org.hibernate.validator.internal.constraintvalidators.MinValidatorForNumber;
import org.hibernate.validator.internal.constraintvalidators.ModCheckValidator;
import org.hibernate.validator.internal.constraintvalidators.NotBlankValidator;
import org.hibernate.validator.internal.constraintvalidators.NotNullValidator;
import org.hibernate.validator.internal.constraintvalidators.NullValidator;
import org.hibernate.validator.internal.constraintvalidators.PastValidatorForCalendar;
import org.hibernate.validator.internal.constraintvalidators.PastValidatorForDate;
import org.hibernate.validator.internal.constraintvalidators.PastValidatorForReadableInstant;
import org.hibernate.validator.internal.constraintvalidators.PastValidatorForReadablePartial;
import org.hibernate.validator.internal.constraintvalidators.PatternValidator;
import org.hibernate.validator.internal.constraintvalidators.SafeHtmlValidator;
import org.hibernate.validator.internal.constraintvalidators.ScriptAssertValidator;
import org.hibernate.validator.internal.constraintvalidators.SizeValidatorForArray;
import org.hibernate.validator.internal.constraintvalidators.SizeValidatorForArraysOfBoolean;
import org.hibernate.validator.internal.constraintvalidators.SizeValidatorForArraysOfByte;
import org.hibernate.validator.internal.constraintvalidators.SizeValidatorForArraysOfChar;
import org.hibernate.validator.internal.constraintvalidators.SizeValidatorForArraysOfDouble;
import org.hibernate.validator.internal.constraintvalidators.SizeValidatorForArraysOfFloat;
import org.hibernate.validator.internal.constraintvalidators.SizeValidatorForArraysOfInt;
import org.hibernate.validator.internal.constraintvalidators.SizeValidatorForArraysOfLong;
import org.hibernate.validator.internal.constraintvalidators.SizeValidatorForCharSequence;
import org.hibernate.validator.internal.constraintvalidators.SizeValidatorForCollection;
import org.hibernate.validator.internal.constraintvalidators.SizeValidatorForMap;
import org.hibernate.validator.internal.constraintvalidators.URLValidator;
import org.hibernate.validator.internal.util.Contracts;
import org.hibernate.validator.internal.util.ReflectionHelper;
import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;

import static org.hibernate.validator.internal.util.CollectionHelper.newArrayList;
import static org.hibernate.validator.internal.util.CollectionHelper.newConcurrentHashMap;
import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES;

/**
 * Keeps track of builtin constraints and their validator implementations, as well as already resolved validator definitions.
 *
 * @author Hardy Ferentschik
 * @author Alaa Nassef
 * @author Gunnar Morling
 */
public class ConstraintHelper {
	public static final String GROUPS = "groups";
	public static final String PAYLOAD = "payload";
	public static final String MESSAGE = "message";
	public static final String VALIDATION_APPLIES_TO = "validationAppliesTo";

	private static final Log log = LoggerFactory.make();
	private static final String JODA_TIME_CLASS_NAME = "org.joda.time.ReadableInstant";

	private final ConcurrentMap, List>> builtinConstraints = newConcurrentHashMap();
	private final ValidatorClassMap validatorClasses = new ValidatorClassMap();

	public ConstraintHelper() {
		List>> constraintList = newArrayList();
		constraintList.add( AssertFalseValidator.class );
		builtinConstraints.put( AssertFalse.class, constraintList );

		constraintList = newArrayList();
		constraintList.add( AssertTrueValidator.class );
		builtinConstraints.put( AssertTrue.class, constraintList );

		constraintList = newArrayList();
		constraintList.add( DecimalMaxValidatorForNumber.class );
		constraintList.add( DecimalMaxValidatorForCharSequence.class );
		builtinConstraints.put( DecimalMax.class, constraintList );

		constraintList = newArrayList();
		constraintList.add( DecimalMinValidatorForNumber.class );
		constraintList.add( DecimalMinValidatorForCharSequence.class );
		builtinConstraints.put( DecimalMin.class, constraintList );

		constraintList = newArrayList();
		constraintList.add( DigitsValidatorForCharSequence.class );
		constraintList.add( DigitsValidatorForNumber.class );
		builtinConstraints.put( Digits.class, constraintList );

		constraintList = newArrayList();
		constraintList.add( FutureValidatorForCalendar.class );
		constraintList.add( FutureValidatorForDate.class );
		if ( isJodaTimeInClasspath() ) {
			constraintList.add( FutureValidatorForReadableInstant.class );
			constraintList.add( FutureValidatorForReadablePartial.class );
		}
		builtinConstraints.put( Future.class, constraintList );

		constraintList = newArrayList();
		constraintList.add( MaxValidatorForNumber.class );
		constraintList.add( MaxValidatorForCharSequence.class );
		builtinConstraints.put( Max.class, constraintList );

		constraintList = newArrayList();
		constraintList.add( MinValidatorForNumber.class );
		constraintList.add( MinValidatorForCharSequence.class );
		builtinConstraints.put( Min.class, constraintList );

		constraintList = newArrayList();
		constraintList.add( NotNullValidator.class );
		builtinConstraints.put( NotNull.class, constraintList );

		constraintList = newArrayList();
		constraintList.add( NullValidator.class );
		builtinConstraints.put( Null.class, constraintList );

		constraintList = newArrayList();
		constraintList.add( PastValidatorForCalendar.class );
		constraintList.add( PastValidatorForDate.class );
		if ( isJodaTimeInClasspath() ) {
			constraintList.add( PastValidatorForReadableInstant.class );
			constraintList.add( PastValidatorForReadablePartial.class );
		}
		builtinConstraints.put( Past.class, constraintList );

		constraintList = newArrayList();
		constraintList.add( PatternValidator.class );
		builtinConstraints.put( Pattern.class, constraintList );

		constraintList = newArrayList();
		constraintList.add( SizeValidatorForCharSequence.class );
		constraintList.add( SizeValidatorForCollection.class );
		constraintList.add( SizeValidatorForArray.class );
		constraintList.add( SizeValidatorForMap.class );
		constraintList.add( SizeValidatorForArraysOfBoolean.class );
		constraintList.add( SizeValidatorForArraysOfByte.class );
		constraintList.add( SizeValidatorForArraysOfChar.class );
		constraintList.add( SizeValidatorForArraysOfDouble.class );
		constraintList.add( SizeValidatorForArraysOfFloat.class );
		constraintList.add( SizeValidatorForArraysOfInt.class );
		constraintList.add( SizeValidatorForArraysOfLong.class );
		builtinConstraints.put( Size.class, constraintList );

		constraintList = newArrayList();
		constraintList.add( EmailValidator.class );
		builtinConstraints.put( Email.class, constraintList );

		constraintList = newArrayList();
		constraintList.add( LengthValidator.class );
		builtinConstraints.put( Length.class, constraintList );

		constraintList = newArrayList();
		constraintList.add( ModCheckValidator.class );
		builtinConstraints.put( ModCheck.class, constraintList );

		constraintList = newArrayList();
		constraintList.add( NotBlankValidator.class );
		builtinConstraints.put( NotBlank.class, constraintList );

		constraintList = newArrayList();
		constraintList.add( SafeHtmlValidator.class );
		builtinConstraints.put( SafeHtml.class, constraintList );

		constraintList = newArrayList();
		constraintList.add( ScriptAssertValidator.class );
		builtinConstraints.put( ScriptAssert.class, constraintList );

		constraintList = newArrayList();
		constraintList.add( URLValidator.class );
		builtinConstraints.put( URL.class, constraintList );
	}

	private  List>> getBuiltInConstraints(Class annotationClass) {
		//safe cause all CV for a given annotation A are CV
		@SuppressWarnings("unchecked")
		final List>> builtInList = (List>>) builtinConstraints
				.get( annotationClass );

		if ( builtInList == null || builtInList.size() == 0 ) {
			throw log.getUnableToFindAnnotationConstraintsException( annotationClass );
		}

		return builtInList;
	}

	private boolean isBuiltinConstraint(Class annotationType) {
		return builtinConstraints.containsKey( annotationType );
	}

	/**
	 * Returns the constraint validator classes for the given constraint
	 * annotation type, as retrieved from
	 *
	 * 
    *
  • {@link Constraint#validatedBy()}, *
  • internally registered validators for built-in constraints and
  • *
  • XML configuration.
  • *
* * The result is cached internally. * * @param annotationType The constraint annotation type. * * @return The validator classes for the given type. */ public
List>> getAllValidatorClasses(Class annotationType) { Contracts.assertNotNull( annotationType, MESSAGES.classCannotBeNull() ); List>> classes = validatorClasses.get( annotationType ); if ( classes == null ) { classes = getDefaultValidatorClasses( annotationType ); List>> cachedValidatorClasses = validatorClasses.putIfAbsent( annotationType, classes ); if ( cachedValidatorClasses != null ) { classes = cachedValidatorClasses; } } return Collections.unmodifiableList( classes ); } /** * Returns those validator classes for the given constraint annotation * matching the given target. * * @param annotationType The annotation of interest. * @param validationTarget The target, either annotated element or parameters. * * @return A list with matching validator classes. */ public List>> findValidatorClasses(Class annotationType, ValidationTarget validationTarget) { List>> validatorClasses = getAllValidatorClasses( annotationType ); List>> matchingValidatorClasses = newArrayList(); for ( Class> validatorClass : validatorClasses ) { if ( supportsValidationTarget( validatorClass, validationTarget ) ) { matchingValidatorClasses.add( validatorClass ); } } return matchingValidatorClasses; } private boolean supportsValidationTarget(Class> validatorClass, ValidationTarget target) { SupportedValidationTarget supportedTargetAnnotation = validatorClass.getAnnotation( SupportedValidationTarget.class ); //by default constraints target the annotated element if ( supportedTargetAnnotation == null ) { return target == ValidationTarget.ANNOTATED_ELEMENT; } return Arrays.asList( supportedTargetAnnotation.value() ).contains( target ); } /** * Registers the given validator classes with the given constraint * annotation type. * * @param annotationType The constraint annotation type * @param definitionClasses The validators to register * @param keepDefaultClasses Whether any default validators should be kept or not */ public void putValidatorClasses(Class annotationType, List>> definitionClasses, boolean keepDefaultClasses) { if ( keepDefaultClasses ) { List>> defaultValidators = getDefaultValidatorClasses( annotationType ); for ( Class> defaultValidator : defaultValidators ) { definitionClasses.add( 0, defaultValidator ); } } validatorClasses.put( annotationType, definitionClasses ); } /** * Checks whether a given annotation is a multi value constraint or not. * * @param annotationType the annotation type to check. * * @return {@code true} if the specified annotation is a multi value constraints, {@code false} * otherwise. */ public boolean isMultiValueConstraint(Class annotationType) { boolean isMultiValueConstraint = false; final Method method = ReflectionHelper.getMethod( annotationType, "value" ); if ( method != null ) { Class returnType = method.getReturnType(); if ( returnType.isArray() && returnType.getComponentType().isAnnotation() ) { @SuppressWarnings("unchecked") Class componentType = (Class) returnType.getComponentType(); if ( isConstraintAnnotation( componentType ) || isBuiltinConstraint( componentType ) ) { isMultiValueConstraint = true; } else { isMultiValueConstraint = false; } } } return isMultiValueConstraint; } /** * Checks whether a given annotation is a multi value constraint and returns the contained constraints if so. * * @param annotation the annotation to check. * * @return A list of constraint annotations or the empty list if annotation is not a multi constraint * annotation. */ public List getMultiValueConstraints(A annotation) { List annotationList = newArrayList(); try { final Method method = ReflectionHelper.getMethod( annotation.getClass(), "value" ); if ( method != null ) { Class returnType = method.getReturnType(); if ( returnType.isArray() && returnType.getComponentType().isAnnotation() ) { Annotation[] annotations = (Annotation[]) method.invoke( annotation ); for ( Annotation a : annotations ) { Class annotationType = a.annotationType(); if ( isConstraintAnnotation( annotationType ) || isBuiltinConstraint( annotationType ) ) { annotationList.add( a ); } } } } } catch ( IllegalAccessException iae ) { // ignore } catch ( InvocationTargetException ite ) { // ignore } return annotationList; } /** * Checks whether the specified annotation is a valid constraint annotation. A constraint annotation has to * fulfill the following conditions: *
    *
  • Must be annotated with {@link Constraint} *
  • Define a message parameter
  • *
  • Define a group parameter
  • *
  • Define a payload parameter
  • *
* * @param annotationType The annotation type to test. * * @return {@code true} if the annotation fulfills the above conditions, {@code false} otherwise. */ public boolean isConstraintAnnotation(Class annotationType) { if ( annotationType.getAnnotation( Constraint.class ) == null ) { return false; } assertMessageParameterExists( annotationType ); assertGroupsParameterExists( annotationType ); assertPayloadParameterExists( annotationType ); assertValidationAppliesToParameterSetUpCorrectly( annotationType ); assertNoParameterStartsWithValid( annotationType ); return true; } private void assertNoParameterStartsWithValid(Class annotationType) { final Method[] methods = ReflectionHelper.getDeclaredMethods( annotationType ); for ( Method m : methods ) { if ( m.getName().startsWith( "valid" ) && !m.getName().equals( VALIDATION_APPLIES_TO ) ) { throw log.getConstraintParametersCannotStartWithValidException(); } } } private void assertPayloadParameterExists(Class annotationType) { try { final Method method = ReflectionHelper.getMethod( annotationType, PAYLOAD ); if ( method == null ) { throw log.getConstraintWithoutMandatoryParameterException( PAYLOAD, annotationType.getName() ); } Class[] defaultPayload = (Class[]) method.getDefaultValue(); if ( defaultPayload.length != 0 ) { throw log.getWrongDefaultValueForPayloadParameterException( annotationType.getName() ); } } catch ( ClassCastException e ) { throw log.getWrongTypeForPayloadParameterException( annotationType.getName(), e ); } } private void assertGroupsParameterExists(Class annotationType) { try { final Method method = ReflectionHelper.getMethod( annotationType, GROUPS ); if ( method == null ) { throw log.getConstraintWithoutMandatoryParameterException( GROUPS, annotationType.getName() ); } Class[] defaultGroups = (Class[]) method.getDefaultValue(); if ( defaultGroups.length != 0 ) { throw log.getWrongDefaultValueForGroupsParameterException( annotationType.getName() ); } } catch ( ClassCastException e ) { throw log.getWrongTypeForGroupsParameterException( annotationType.getName(), e ); } } private void assertMessageParameterExists(Class annotationType) { final Method method = ReflectionHelper.getMethod( annotationType, MESSAGE ); if ( method == null ) { throw log.getConstraintWithoutMandatoryParameterException( MESSAGE, annotationType.getName() ); } if ( method.getReturnType() != String.class ) { throw log.getWrongTypeForMessageParameterException( annotationType.getName() ); } } private void assertValidationAppliesToParameterSetUpCorrectly(Class annotationType) { boolean hasGenericValidators = !findValidatorClasses( annotationType, ValidationTarget.ANNOTATED_ELEMENT ).isEmpty(); boolean hasCrossParameterValidator = !findValidatorClasses( annotationType, ValidationTarget.PARAMETERS ).isEmpty(); final Method method = ReflectionHelper.getMethod( annotationType, VALIDATION_APPLIES_TO ); if ( hasGenericValidators && hasCrossParameterValidator ) { if ( method == null ) { throw log.getGenericAndCrossParameterConstraintDoesNotDefineValidationAppliesToParameterException( annotationType.getName() ); } if ( method.getReturnType() != ConstraintTarget.class ) { throw log.getValidationAppliesToParameterMustHaveReturnTypeConstraintTargetException( annotationType.getName() ); } ConstraintTarget defaultValue = (ConstraintTarget) method.getDefaultValue(); if ( defaultValue != ConstraintTarget.IMPLICIT ) { throw log.getValidationAppliesToParameterMustHaveDefaultValueImplicitException( annotationType.getName() ); } } else if ( method != null ) { throw log.getValidationAppliesToParameterMustNotBeDefinedForNonGenericAndCrossParameterConstraintException( annotationType.getName() ); } } public boolean isConstraintComposition(Class annotationType) { return annotationType == ConstraintComposition.class; } private boolean isJodaTimeInClasspath() { boolean isInClasspath; try { ReflectionHelper.loadClass( JODA_TIME_CLASS_NAME, this.getClass() ); isInClasspath = true; } catch ( ValidationException e ) { isInClasspath = false; } return isInClasspath; } /** * Returns the default validators for the given constraint type. * * @param annotationType The constraint annotation type. * * @return A list with the default validators as retrieved from * {@link Constraint#validatedBy()} or the list of validators for * built-in constraints. */ private
List>> getDefaultValidatorClasses(Class annotationType) { if ( isBuiltinConstraint( annotationType ) ) { return getBuiltInConstraints( annotationType ); } else { @SuppressWarnings("unchecked") Class>[] validatedBy = (Class>[]) annotationType .getAnnotation( Constraint.class ) .validatedBy(); return Arrays.asList( validatedBy ); } } /** * A type-safe wrapper around a concurrent map from constraint types to * associated validator classes. The casts are safe as data is added trough * the typed API only. * * @author Gunnar Morling */ @SuppressWarnings("unchecked") private static class ValidatorClassMap { private final ConcurrentMap, List>> constraintValidatorClasses = newConcurrentHashMap(); private List>> get(Class annotationType) { return (List>>) constraintValidatorClasses.get( annotationType ); } private void put(Class annotationType, List>> validatorClasses) { constraintValidatorClasses.put( annotationType, validatorClasses ); } private List>> putIfAbsent(Class annotationType, List>> classes) { return (List>>) constraintValidatorClasses.putIfAbsent( annotationType, classes ); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy