/** * Check that the number being validated is less than or equal to the maximum value specified. * * @author Hardy Ferentschik */ public class DecimalMaxValidatorForNumber implements ConstraintValidator<DecimalMax, Number> { private static final Log log = LoggerFactory.make(); private BigDecimal maxValue; public void initialize(DecimalMax maxValue) { try { this.maxValue = new BigDecimal(maxValue.value()); } catch (NumberFormatException nfe) { throw log.getInvalidBigDecimalFormatException(maxValue.value(), nfe); } } public boolean isValid(Number value, ConstraintValidatorContext constraintValidatorContext) { // null values are valid if (value == null) { return true; } if (value instanceof BigDecimal) { return ((BigDecimal) value).compareTo(maxValue) != 1; } else if (value instanceof BigInteger) { return (new BigDecimal((BigInteger) value)).compareTo(maxValue) != 1; } else if (value instanceof Long) { return (BigDecimal.valueOf(value.longValue()).compareTo(maxValue)) != 1; } else { return (BigDecimal.valueOf(value.doubleValue()).compareTo(maxValue)) != 1; } } }
/** * @author Emmanuel Bernard * @author Hardy Ferentschik */ public final class GetAnnotationParameter<T> implements PrivilegedAction<T> { private static final Log log = LoggerFactory.make(); private final Annotation annotation; private final String parameterName; private final Class<T> type; public static <T> GetAnnotationParameter<T> action( Annotation annotation, String parameterName, Class<T> type) { return new GetAnnotationParameter<T>(annotation, parameterName, type); } private GetAnnotationParameter(Annotation annotation, String parameterName, Class<T> type) { this.annotation = annotation; this.parameterName = parameterName; this.type = type; } @Override public T run() { try { Method m = annotation.getClass().getMethod(parameterName); m.setAccessible(true); Object o = m.invoke(annotation); if (type.isAssignableFrom(o.getClass())) { return (T) o; } else { throw log.getWrongParameterTypeException(type.getName(), o.getClass().getName()); } } catch (NoSuchMethodException e) { throw log.getUnableToFindAnnotationParameterException(parameterName, e); } catch (IllegalAccessException e) { throw log.getUnableToGetAnnotationParameterException( parameterName, annotation.getClass().getName(), e); } catch (InvocationTargetException e) { throw log.getUnableToGetAnnotationParameterException( parameterName, annotation.getClass().getName(), e); } } }
/** * ModCheckBase contains all shared methods and options used by Mod Check Validators * * <p>http://en.wikipedia.org/wiki/Check_digit * * @author George Gastaldi * @author Hardy Ferentschik * @author Victor Rezende dos Santos */ public abstract class ModCheckBase { private static final Log log = LoggerFactory.make(); private static final Pattern NUMBERS_ONLY_REGEXP = Pattern.compile("[^0-9]"); private static final int DEC_RADIX = 10; /** The start index for the checksum calculation */ private int startIndex; /** The end index for the checksum calculation */ private int endIndex; /** The index of the checksum digit */ private int checkDigitIndex; private boolean ignoreNonDigitCharacters; public boolean isValid(final CharSequence value, final ConstraintValidatorContext context) { if (value == null) { return true; } String valueAsString = value.toString(); String digitsAsString; char checkDigit; try { digitsAsString = extractVerificationString(valueAsString); checkDigit = extractCheckDigit(valueAsString); } catch (IndexOutOfBoundsException e) { return false; } digitsAsString = stripNonDigitsIfRequired(digitsAsString); List<Integer> digits; try { digits = extractDigits(digitsAsString); } catch (NumberFormatException e) { return false; } return this.isCheckDigitValid(digits, checkDigit); } public abstract boolean isCheckDigitValid(List<Integer> digits, char checkDigit); protected void initialize( int startIndex, int endIndex, int checkDigitIndex, boolean ignoreNonDigitCharacters) { this.startIndex = startIndex; this.endIndex = endIndex; this.checkDigitIndex = checkDigitIndex; this.ignoreNonDigitCharacters = ignoreNonDigitCharacters; this.validateOptions(); } /** * Returns the numeric {@code int} value of a {@code char} * * @param value the input {@code char} to be parsed * @return the numeric {@code int} value represented by the character. * @throws NumberFormatException in case character is not a digit */ protected int extractDigit(char value) throws NumberFormatException { if (Character.isDigit(value)) { return Character.digit(value, DEC_RADIX); } else { throw log.getCharacterIsNotADigitException(value); } } /** * Parses the {@link String} value as a {@link List} of {@link Integer} objects * * @param value the input string to be parsed * @return List of {@code Integer} objects. * @throws NumberFormatException in case any of the characters is not a digit */ private List<Integer> extractDigits(final String value) throws NumberFormatException { List<Integer> digits = new ArrayList<Integer>(value.length()); char[] chars = value.toCharArray(); for (char c : chars) { digits.add(extractDigit(c)); } return digits; } private boolean validateOptions() { if (this.startIndex < 0) { throw log.getStartIndexCannotBeNegativeException(this.startIndex); } if (this.endIndex < 0) { throw log.getEndIndexCannotBeNegativeException(this.endIndex); } if (this.startIndex > this.endIndex) { throw log.getInvalidRangeException(this.startIndex, this.endIndex); } if (this.checkDigitIndex > 0 && this.startIndex <= this.checkDigitIndex && this.endIndex > this.checkDigitIndex) { throw log.getInvalidCheckDigitException(this.startIndex, this.endIndex); } return true; } private String stripNonDigitsIfRequired(String value) { if (ignoreNonDigitCharacters) { return NUMBERS_ONLY_REGEXP.matcher(value).replaceAll(""); } else { return value; } } private String extractVerificationString(String value) throws IndexOutOfBoundsException { // the string contains the check digit, just return the digits to verify if (endIndex == Integer.MAX_VALUE) { return value.substring(0, value.length() - 1); } else if (checkDigitIndex == -1) { return value.substring(startIndex, endIndex); } else { return value.substring(startIndex, endIndex + 1); } } private char extractCheckDigit(String value) throws IndexOutOfBoundsException { // take last character of string to be validated unless the index is given explicitly if (checkDigitIndex == -1) { if (endIndex == Integer.MAX_VALUE) { return value.charAt(value.length() - 1); } else { return value.charAt(endIndex); } } else { return value.charAt(checkDigitIndex); } } }
/** * A CDI portable extension which integrates Bean Validation with CDI. It registers the following * objects: * * <ul> * <li>Beans for {@link ValidatorFactory} and {@link Validator} representing default validator * factory and validator as configured via {@code META-INF/validation.xml}. These beans will * have the {@code Default} qualifier and in addition the {@code HibernateValidator} qualifier * if Hibernate Validator is the default validation provider. * <li>In case Hibernate Validator is <em>not</em> the default provider, another pair of beans * will be registered in addition which are qualified with the {@code HibernateValidator} * qualifier. * </ul> * * Neither of these beans will be registered in case there is already another bean with the same * type and qualifier(s), e.g. registered by another portable extension or the application itself. * * @author Gunnar Morling * @author Hardy Ferentschik */ public class ValidationExtension implements Extension { private static final Log log = LoggerFactory.make(); private static final EnumSet<ExecutableType> ALL_EXECUTABLE_TYPES = EnumSet.of( ExecutableType.CONSTRUCTORS, ExecutableType.NON_GETTER_METHODS, ExecutableType.GETTER_METHODS); private static final EnumSet<ExecutableType> DEFAULT_EXECUTABLE_TYPES = EnumSet.of(ExecutableType.CONSTRUCTORS, ExecutableType.NON_GETTER_METHODS); @SuppressWarnings("serial") private final Annotation defaultQualifier = new AnnotationLiteral<Default>() {}; @SuppressWarnings("serial") private final Annotation hibernateValidatorQualifier = new AnnotationLiteral<HibernateValidator>() {}; private final ExecutableHelper executableHelper; /** Used for identifying constrained classes */ private final Validator validator; private final ValidatorFactory validatorFactory; private final Set<ExecutableType> globalExecutableTypes; private final boolean isExecutableValidationEnabled; private Bean<?> defaultValidatorFactoryBean; private Bean<?> hibernateValidatorFactoryBean; private Bean<?> defaultValidatorBean; private Bean<?> hibernateValidatorBean; public ValidationExtension() { Configuration<?> config = Validation.byDefaultProvider().configure(); BootstrapConfiguration bootstrap = config.getBootstrapConfiguration(); globalExecutableTypes = bootstrap.getDefaultValidatedExecutableTypes(); isExecutableValidationEnabled = bootstrap.isExecutableValidationEnabled(); validatorFactory = config.buildValidatorFactory(); validator = validatorFactory.getValidator(); executableHelper = new ExecutableHelper(new TypeResolutionHelper()); } /** * Used to register the method validation interceptor binding annotation. * * @param beforeBeanDiscoveryEvent event fired before the bean discovery process starts * @param beanManager the bean manager. */ public void beforeBeanDiscovery( @Observes BeforeBeanDiscovery beforeBeanDiscoveryEvent, final BeanManager beanManager) { Contracts.assertNotNull( beforeBeanDiscoveryEvent, "The BeforeBeanDiscovery event cannot be null"); Contracts.assertNotNull(beanManager, "The BeanManager cannot be null"); // Register the interceptor explicitly. This way, no beans.xml is needed AnnotatedType<ValidationInterceptor> annotatedType = beanManager.createAnnotatedType(ValidationInterceptor.class); beforeBeanDiscoveryEvent.addAnnotatedType(annotatedType); } /** * Registers beans for {@code ValidatorFactory} and {@code Validator} if not yet present. * * @param afterBeanDiscoveryEvent event fired after the bean discovery phase. * @param beanManager the bean manager. */ public void afterBeanDiscovery( @Observes AfterBeanDiscovery afterBeanDiscoveryEvent, BeanManager beanManager) { Contracts.assertNotNull(afterBeanDiscoveryEvent, "The AfterBeanDiscovery event cannot be null"); Contracts.assertNotNull(beanManager, "The BeanManager cannot be null"); ValidationProviderHelper defaultProviderHelper = ValidationProviderHelper.forDefaultProvider(validatorFactory); ValidationProviderHelper hvProviderHelper = ValidationProviderHelper.forHibernateValidator(); // register default VF if none has been provided by the application or another PE if (defaultValidatorFactoryBean == null) { defaultValidatorFactoryBean = new ValidatorFactoryBean(beanManager, defaultProviderHelper); if (hibernateValidatorFactoryBean == null && defaultProviderHelper.isHibernateValidator()) { hibernateValidatorFactoryBean = defaultValidatorFactoryBean; } afterBeanDiscoveryEvent.addBean(defaultValidatorFactoryBean); } // register VF with @HibernateValidator qualifier in case it hasn't been contributed by the // application and the // default VF registered by ourselves isn't for Hibernate Validator if (hibernateValidatorFactoryBean == null) { hibernateValidatorFactoryBean = new ValidatorFactoryBean(beanManager, hvProviderHelper); afterBeanDiscoveryEvent.addBean(hibernateValidatorFactoryBean); } // register default validator if required if (defaultValidatorBean == null) { defaultValidatorBean = new ValidatorBean(beanManager, defaultValidatorFactoryBean, defaultProviderHelper); if (hibernateValidatorBean == null && defaultProviderHelper.isHibernateValidator()) { hibernateValidatorBean = defaultValidatorBean; } afterBeanDiscoveryEvent.addBean(defaultValidatorBean); } // register validator with @HibernateValidator if required if (hibernateValidatorBean == null) { hibernateValidatorBean = new ValidatorBean(beanManager, hibernateValidatorFactoryBean, hvProviderHelper); afterBeanDiscoveryEvent.addBean(hibernateValidatorBean); } } /** * Watches the {@code ProcessBean} event in order to determine whether beans for {@code * ValidatorFactory} and {@code Validator} already have been registered by some other component. * * @param processBeanEvent event fired for each enabled bean. */ public void processBean(@Observes ProcessBean<?> processBeanEvent) { Contracts.assertNotNull(processBeanEvent, "The ProcessBean event cannot be null"); Bean<?> bean = processBeanEvent.getBean(); if (bean.getTypes().contains(ValidatorFactory.class) || bean instanceof ValidatorFactoryBean) { if (bean.getQualifiers().contains(defaultQualifier)) { defaultValidatorFactoryBean = bean; } if (bean.getQualifiers().contains(hibernateValidatorQualifier)) { hibernateValidatorFactoryBean = bean; } } else if (bean.getTypes().contains(Validator.class) || bean instanceof ValidatorBean) { if (bean.getQualifiers().contains(defaultQualifier)) { defaultValidatorBean = bean; } if (bean.getQualifiers().contains(hibernateValidatorQualifier)) { hibernateValidatorBean = bean; } } } /** * Used to register the method validation interceptor bindings. * * @param processAnnotatedTypeEvent event fired for each annotated type * @param <T> the annotated type */ public <T> void processAnnotatedType( @Observes @WithAnnotations({Constraint.class, Valid.class, ValidateOnExecution.class}) ProcessAnnotatedType<T> processAnnotatedTypeEvent) { Contracts.assertNotNull( processAnnotatedTypeEvent, "The ProcessAnnotatedType event cannot be null"); // validation globally disabled if (!isExecutableValidationEnabled) { return; } AnnotatedType<T> type = processAnnotatedTypeEvent.getAnnotatedType(); Set<AnnotatedCallable<? super T>> constrainedCallables = determineConstrainedCallables(type); if (!constrainedCallables.isEmpty()) { ValidationEnabledAnnotatedType<T> wrappedType = new ValidationEnabledAnnotatedType<T>(type, constrainedCallables); processAnnotatedTypeEvent.setAnnotatedType(wrappedType); } } private <T> Set<AnnotatedCallable<? super T>> determineConstrainedCallables( AnnotatedType<T> type) { Set<AnnotatedCallable<? super T>> callables = newHashSet(); BeanDescriptor beanDescriptor = validator.getConstraintsForClass(type.getJavaClass()); determineConstrainedConstructors(type, beanDescriptor, callables); determineConstrainedMethods(type, beanDescriptor, callables); return callables; } private <T> void determineConstrainedMethods( AnnotatedType<T> type, BeanDescriptor beanDescriptor, Set<AnnotatedCallable<? super T>> callables) { List<Method> overriddenAndImplementedMethods = InheritedMethodsHelper.getAllMethods(type.getJavaClass()); for (AnnotatedMethod<? super T> annotatedMethod : type.getMethods()) { Method method = annotatedMethod.getJavaMember(); boolean isGetter = ReflectionHelper.isGetterMethod(method); // obtain @ValidateOnExecution from the top-most method in the hierarchy Method methodForExecutableTypeRetrieval = replaceWithOverriddenOrInterfaceMethod(method, overriddenAndImplementedMethods); EnumSet<ExecutableType> classLevelExecutableTypes = executableTypesDefinedOnType(methodForExecutableTypeRetrieval.getDeclaringClass()); EnumSet<ExecutableType> memberLevelExecutableType = executableTypesDefinedOnMethod(methodForExecutableTypeRetrieval, isGetter); ExecutableType currentExecutableType = isGetter ? ExecutableType.GETTER_METHODS : ExecutableType.NON_GETTER_METHODS; // validation is enabled per default, so explicit configuration can just veto whether // validation occurs if (veto(classLevelExecutableTypes, memberLevelExecutableType, currentExecutableType)) { continue; } boolean needsValidation; if (isGetter) { needsValidation = isGetterConstrained(method, beanDescriptor); } else { needsValidation = isNonGetterConstrained(method, beanDescriptor); } if (needsValidation) { callables.add(annotatedMethod); } } } private <T> void determineConstrainedConstructors( AnnotatedType<T> type, BeanDescriptor beanDescriptor, Set<AnnotatedCallable<? super T>> callables) { Class<?> clazz = type.getJavaClass(); EnumSet<ExecutableType> classLevelExecutableTypes = executableTypesDefinedOnType(clazz); for (AnnotatedConstructor<T> annotatedConstructor : type.getConstructors()) { Constructor<?> constructor = annotatedConstructor.getJavaMember(); EnumSet<ExecutableType> memberLevelExecutableType = executableTypesDefinedOnConstructor(constructor); if (veto(classLevelExecutableTypes, memberLevelExecutableType, ExecutableType.CONSTRUCTORS)) { continue; } if (beanDescriptor.getConstraintsForConstructor(constructor.getParameterTypes()) != null) { callables.add(annotatedConstructor); } } } private boolean isNonGetterConstrained(Method method, BeanDescriptor beanDescriptor) { return beanDescriptor.getConstraintsForMethod(method.getName(), method.getParameterTypes()) != null; } private boolean isGetterConstrained(Method method, BeanDescriptor beanDescriptor) { String propertyName = ReflectionHelper.getPropertyName(method); PropertyDescriptor propertyDescriptor = beanDescriptor.getConstraintsForProperty(propertyName); return propertyDescriptor != null && propertyDescriptor.findConstraints().declaredOn(ElementType.METHOD).hasConstraints(); } private boolean veto( EnumSet<ExecutableType> classLevelExecutableTypes, EnumSet<ExecutableType> memberLevelExecutableType, ExecutableType currentExecutableType) { if (!memberLevelExecutableType.isEmpty()) { return !memberLevelExecutableType.contains(currentExecutableType) && !memberLevelExecutableType.contains(ExecutableType.IMPLICIT); } if (!classLevelExecutableTypes.isEmpty()) { return !classLevelExecutableTypes.contains(currentExecutableType) && !classLevelExecutableTypes.contains(ExecutableType.IMPLICIT); } return !globalExecutableTypes.contains(currentExecutableType); } private EnumSet<ExecutableType> executableTypesDefinedOnType(Class<?> clazz) { ValidateOnExecution validateOnExecutionAnnotation = clazz.getAnnotation(ValidateOnExecution.class); EnumSet<ExecutableType> executableTypes = commonExecutableTypeChecks(validateOnExecutionAnnotation); if (executableTypes.contains(ExecutableType.IMPLICIT)) { return DEFAULT_EXECUTABLE_TYPES; } return executableTypes; } private EnumSet<ExecutableType> executableTypesDefinedOnMethod(Method method, boolean isGetter) { ValidateOnExecution validateOnExecutionAnnotation = method.getAnnotation(ValidateOnExecution.class); EnumSet<ExecutableType> executableTypes = commonExecutableTypeChecks(validateOnExecutionAnnotation); if (executableTypes.contains(ExecutableType.IMPLICIT)) { if (isGetter) { executableTypes.add(ExecutableType.GETTER_METHODS); } else { executableTypes.add(ExecutableType.NON_GETTER_METHODS); } } return executableTypes; } private EnumSet<ExecutableType> executableTypesDefinedOnConstructor(Constructor<?> constructor) { ValidateOnExecution validateOnExecutionAnnotation = constructor.getAnnotation(ValidateOnExecution.class); EnumSet<ExecutableType> executableTypes = commonExecutableTypeChecks(validateOnExecutionAnnotation); if (executableTypes.contains(ExecutableType.IMPLICIT)) { executableTypes.add(ExecutableType.CONSTRUCTORS); } return executableTypes; } private EnumSet<ExecutableType> commonExecutableTypeChecks( ValidateOnExecution validateOnExecutionAnnotation) { if (validateOnExecutionAnnotation == null) { return EnumSet.noneOf(ExecutableType.class); } EnumSet<ExecutableType> executableTypes = EnumSet.noneOf(ExecutableType.class); if (validateOnExecutionAnnotation.type().length == 0) { // HV-757 executableTypes.add(ExecutableType.NONE); } else { Collections.addAll(executableTypes, validateOnExecutionAnnotation.type()); } // IMPLICIT cannot be mixed 10.1.2 of spec - Mixing IMPLICIT and other executable types is // illegal if (executableTypes.contains(ExecutableType.IMPLICIT) && executableTypes.size() > 1) { throw log.getMixingImplicitWithOtherExecutableTypesException(); } // NONE can be removed 10.1.2 of spec - A list containing NONE and other types of executables is // equivalent to a // list containing the types of executables without NONE. if (executableTypes.contains(ExecutableType.NONE) && executableTypes.size() > 1) { executableTypes.remove(ExecutableType.NONE); } // 10.1.2 of spec - A list containing ALL and other types of executables is equivalent to a list // containing only ALL if (executableTypes.contains(ExecutableType.ALL)) { executableTypes = ALL_EXECUTABLE_TYPES; } return executableTypes; } public Method replaceWithOverriddenOrInterfaceMethod( Method method, List<Method> allMethodsOfType) { LinkedList<Method> list = new LinkedList<Method>(allMethodsOfType); Iterator<Method> iterator = list.descendingIterator(); while (iterator.hasNext()) { Method overriddenOrInterfaceMethod = iterator.next(); if (executableHelper.overrides(method, overriddenOrInterfaceMethod)) { if (method.getAnnotation(ValidateOnExecution.class) != null) { throw log.getValidateOnExecutionOnOverriddenOrInterfaceMethodException(method); } return overriddenOrInterfaceMethod; } } return method; } }
/** * Resource bundle backed message interpolator. * * @author Emmanuel Bernard * @author Hardy Ferentschik * @author Gunnar Morling * @author Adam Stawicki */ public abstract class AbstractMessageInterpolator implements MessageInterpolator { private static final Log log = LoggerFactory.make(); /** The default initial capacity for this cache. */ private static final int DEFAULT_INITIAL_CAPACITY = 100; /** The default load factor for this cache. */ private static final float DEFAULT_LOAD_FACTOR = 0.75f; /** The default concurrency level for this cache. */ private static final int DEFAULT_CONCURRENCY_LEVEL = 16; /** The name of the default message bundle. */ private static final String DEFAULT_VALIDATION_MESSAGES = "org.hibernate.validator.ValidationMessages"; /** The name of the user-provided message bundle as defined in the specification. */ public static final String USER_VALIDATION_MESSAGES = "ValidationMessages"; /** The default locale in the current JVM. */ private final Locale defaultLocale; /** Loads user-specified resource bundles. */ private final ResourceBundleLocator userResourceBundleLocator; /** Loads built-in resource bundles. */ private final ResourceBundleLocator defaultResourceBundleLocator; /** Step 1-3 of message interpolation can be cached. We do this in this map. */ private final ConcurrentReferenceHashMap<LocalizedMessage, String> resolvedMessages; /** * Step 4 of message interpolation replaces message parameters. The token list for message * parameters is cached in this map. */ private final ConcurrentReferenceHashMap<String, List<Token>> tokenizedParameterMessages; /** * Step 5 of message interpolation replaces EL expressions. The token list for EL expressions is * cached in this map. */ private final ConcurrentReferenceHashMap<String, List<Token>> tokenizedELMessages; /** Flag indicating whether this interpolator should cache some of the interpolation steps. */ private final boolean cachingEnabled; private static final Pattern LEFT_BRACE = Pattern.compile("\\{", Pattern.LITERAL); private static final Pattern RIGHT_BRACE = Pattern.compile("\\}", Pattern.LITERAL); private static final Pattern SLASH = Pattern.compile("\\\\", Pattern.LITERAL); private static final Pattern DOLLAR = Pattern.compile("\\$", Pattern.LITERAL); public AbstractMessageInterpolator() { this(null); } public AbstractMessageInterpolator(ResourceBundleLocator userResourceBundleLocator) { this(userResourceBundleLocator, true); } public AbstractMessageInterpolator( ResourceBundleLocator userResourceBundleLocator, boolean cacheMessages) { defaultLocale = Locale.getDefault(); if (userResourceBundleLocator == null) { this.userResourceBundleLocator = new PlatformResourceBundleLocator(USER_VALIDATION_MESSAGES); } else { this.userResourceBundleLocator = userResourceBundleLocator; } this.defaultResourceBundleLocator = new PlatformResourceBundleLocator(DEFAULT_VALIDATION_MESSAGES); this.cachingEnabled = cacheMessages; if (cachingEnabled) { this.resolvedMessages = new ConcurrentReferenceHashMap<LocalizedMessage, String>( DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, SOFT, SOFT, EnumSet.noneOf(ConcurrentReferenceHashMap.Option.class)); this.tokenizedParameterMessages = new ConcurrentReferenceHashMap<String, List<Token>>( DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, SOFT, SOFT, EnumSet.noneOf(ConcurrentReferenceHashMap.Option.class)); this.tokenizedELMessages = new ConcurrentReferenceHashMap<String, List<Token>>( DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, SOFT, SOFT, EnumSet.noneOf(ConcurrentReferenceHashMap.Option.class)); } else { resolvedMessages = null; tokenizedParameterMessages = null; tokenizedELMessages = null; } } @Override public String interpolate(String message, Context context) { // probably no need for caching, but it could be done by parameters since the map // is immutable and uniquely built per Validation definition, the comparison has to be based on // == and not equals though String interpolatedMessage = message; try { interpolatedMessage = interpolateMessage(message, context, defaultLocale); } catch (MessageDescriptorFormatException e) { log.warn(e.getMessage()); } return interpolatedMessage; } @Override public String interpolate(String message, Context context, Locale locale) { String interpolatedMessage = message; try { interpolatedMessage = interpolateMessage(message, context, locale); } catch (ValidationException e) { log.warn(e.getMessage()); } return interpolatedMessage; } /** * Runs the message interpolation according to algorithm specified in the Bean Validation * specification. <br> * Note: <br> * Look-ups in user bundles is recursive whereas look-ups in default bundle are not! * * @param message the message to interpolate * @param context the context for this interpolation * @param locale the {@code Locale} to use for the resource bundle. * @return the interpolated message. */ private String interpolateMessage(String message, Context context, Locale locale) throws MessageDescriptorFormatException { LocalizedMessage localisedMessage = new LocalizedMessage(message, locale); String resolvedMessage = null; if (cachingEnabled) { resolvedMessage = resolvedMessages.get(localisedMessage); } // if the message is not already in the cache we have to run step 1-3 of the message resolution if (resolvedMessage == null) { ResourceBundle userResourceBundle = userResourceBundleLocator.getResourceBundle(locale); ResourceBundle defaultResourceBundle = defaultResourceBundleLocator.getResourceBundle(locale); String userBundleResolvedMessage; resolvedMessage = message; boolean evaluatedDefaultBundleOnce = false; do { // search the user bundle recursive (step1) userBundleResolvedMessage = interpolateBundleMessage(resolvedMessage, userResourceBundle, locale, true); // exit condition - we have at least tried to validate against the default bundle and there // was no // further replacements if (evaluatedDefaultBundleOnce && !hasReplacementTakenPlace(userBundleResolvedMessage, resolvedMessage)) { break; } // search the default bundle non recursive (step2) resolvedMessage = interpolateBundleMessage( userBundleResolvedMessage, defaultResourceBundle, locale, false); evaluatedDefaultBundleOnce = true; } while (true); } // cache resolved message if (cachingEnabled) { String cachedResolvedMessage = resolvedMessages.putIfAbsent(localisedMessage, resolvedMessage); if (cachedResolvedMessage != null) { resolvedMessage = cachedResolvedMessage; } } // resolve parameter expressions (step 4) List<Token> tokens = null; if (cachingEnabled) { tokens = tokenizedParameterMessages.get(resolvedMessage); } if (tokens == null) { TokenCollector tokenCollector = new TokenCollector(resolvedMessage, InterpolationTermType.PARAMETER); tokens = tokenCollector.getTokenList(); if (cachingEnabled) { tokenizedParameterMessages.putIfAbsent(resolvedMessage, tokens); } } resolvedMessage = interpolateExpression(new TokenIterator(tokens), context, locale); // resolve EL expressions (step 5) tokens = null; if (cachingEnabled) { tokens = tokenizedELMessages.get(resolvedMessage); } if (tokens == null) { TokenCollector tokenCollector = new TokenCollector(resolvedMessage, InterpolationTermType.EL); tokens = tokenCollector.getTokenList(); if (cachingEnabled) { tokenizedELMessages.putIfAbsent(resolvedMessage, tokens); } } resolvedMessage = interpolateExpression(new TokenIterator(tokens), context, locale); // last but not least we have to take care of escaped literals resolvedMessage = replaceEscapedLiterals(resolvedMessage); return resolvedMessage; } private String replaceEscapedLiterals(String resolvedMessage) { resolvedMessage = LEFT_BRACE.matcher(resolvedMessage).replaceAll("{"); resolvedMessage = RIGHT_BRACE.matcher(resolvedMessage).replaceAll("}"); resolvedMessage = SLASH.matcher(resolvedMessage).replaceAll(Matcher.quoteReplacement("\\")); resolvedMessage = DOLLAR.matcher(resolvedMessage).replaceAll(Matcher.quoteReplacement("$")); return resolvedMessage; } private boolean hasReplacementTakenPlace(String origMessage, String newMessage) { return !origMessage.equals(newMessage); } private String interpolateBundleMessage( String message, ResourceBundle bundle, Locale locale, boolean recursive) throws MessageDescriptorFormatException { TokenCollector tokenCollector = new TokenCollector(message, InterpolationTermType.PARAMETER); TokenIterator tokenIterator = new TokenIterator(tokenCollector.getTokenList()); while (tokenIterator.hasMoreInterpolationTerms()) { String term = tokenIterator.nextInterpolationTerm(); String resolvedParameterValue = resolveParameter(term, bundle, locale, recursive); tokenIterator.replaceCurrentInterpolationTerm(resolvedParameterValue); } return tokenIterator.getInterpolatedMessage(); } private String interpolateExpression(TokenIterator tokenIterator, Context context, Locale locale) throws MessageDescriptorFormatException { while (tokenIterator.hasMoreInterpolationTerms()) { String term = tokenIterator.nextInterpolationTerm(); String resolvedExpression = interpolate(context, locale, term); tokenIterator.replaceCurrentInterpolationTerm(resolvedExpression); } return tokenIterator.getInterpolatedMessage(); } public abstract String interpolate(Context context, Locale locale, String term); private String resolveParameter( String parameterName, ResourceBundle bundle, Locale locale, boolean recursive) throws MessageDescriptorFormatException { String parameterValue; try { if (bundle != null) { parameterValue = bundle.getString(removeCurlyBraces(parameterName)); if (recursive) { parameterValue = interpolateBundleMessage(parameterValue, bundle, locale, recursive); } } else { parameterValue = parameterName; } } catch (MissingResourceException e) { // return parameter itself parameterValue = parameterName; } return parameterValue; } private String removeCurlyBraces(String parameter) { return parameter.substring(1, parameter.length() - 1); } }
/** * A constraint mapping creational context which allows to select the parameter or return value to * which the next operations shall apply. * * @author Kevin Pollet <[email protected]> (C) 2011 SERLI * @author Gunnar Morling */ class ExecutableConstraintMappingContextImpl implements ConstructorConstraintMappingContext, MethodConstraintMappingContext { private static final Log log = LoggerFactory.make(); private final TypeConstraintMappingContextImpl<?> typeContext; private final ExecutableElement executable; private final ParameterConstraintMappingContextImpl[] parameterContexts; private ReturnValueConstraintMappingContextImpl returnValueContext; private CrossParameterConstraintMappingContextImpl crossParameterContext; ExecutableConstraintMappingContextImpl( TypeConstraintMappingContextImpl<?> typeContext, Constructor<?> constructor) { this(typeContext, ExecutableElement.forConstructor(constructor)); } ExecutableConstraintMappingContextImpl( TypeConstraintMappingContextImpl<?> typeContext, Method method) { this(typeContext, ExecutableElement.forMethod(method)); } private ExecutableConstraintMappingContextImpl( TypeConstraintMappingContextImpl<?> typeContext, ExecutableElement executable) { this.typeContext = typeContext; this.executable = executable; this.parameterContexts = new ParameterConstraintMappingContextImpl[executable.getParameterTypes().length]; } @Override public ParameterConstraintMappingContext parameter(int index) { if (index < 0 || index >= executable.getParameterTypes().length) { throw log.getInvalidExecutableParameterIndexException(executable.getAsString(), index); } ParameterConstraintMappingContextImpl context = parameterContexts[index]; if (context != null) { throw log.getParameterHasAlreadyBeConfiguredViaProgrammaticApiException( typeContext.getBeanClass().getName(), executable.getAsString(), index); } context = new ParameterConstraintMappingContextImpl(this, index); parameterContexts[index] = context; return context; } @Override public CrossParameterConstraintMappingContext crossParameter() { if (crossParameterContext != null) { throw log.getCrossParameterElementHasAlreadyBeConfiguredViaProgrammaticApiException( typeContext.getBeanClass().getName(), executable.getAsString()); } crossParameterContext = new CrossParameterConstraintMappingContextImpl(this); return crossParameterContext; } @Override public ReturnValueConstraintMappingContext returnValue() { if (returnValueContext != null) { throw log.getReturnValueHasAlreadyBeConfiguredViaProgrammaticApiException( typeContext.getBeanClass().getName(), executable.getAsString()); } returnValueContext = new ReturnValueConstraintMappingContextImpl(this); return returnValueContext; } public ExecutableElement getExecutable() { return executable; } public TypeConstraintMappingContextImpl<?> getTypeContext() { return typeContext; } public ConstrainedElement build( ConstraintHelper constraintHelper, ParameterNameProvider parameterNameProvider) { // TODO HV-919 Support specification of type parameter constraints via XML and API return new ConstrainedExecutable( ConfigurationSource.API, ConstraintLocation.forReturnValue(executable), getParameters(constraintHelper, parameterNameProvider), crossParameterContext != null ? crossParameterContext.getConstraints(constraintHelper) : Collections.<MetaConstraint<?>>emptySet(), returnValueContext != null ? returnValueContext.getConstraints(constraintHelper) : Collections.<MetaConstraint<?>>emptySet(), Collections.<MetaConstraint<?>>emptySet(), returnValueContext != null ? returnValueContext.getGroupConversions() : Collections.<Class<?>, Class<?>>emptyMap(), returnValueContext != null ? returnValueContext.isCascading() : false, returnValueContext != null ? returnValueContext.unwrapMode() : UnwrapMode.AUTOMATIC); } private List<ConstrainedParameter> getParameters( ConstraintHelper constraintHelper, ParameterNameProvider parameterNameProvider) { List<ConstrainedParameter> constrainedParameters = newArrayList(); for (int i = 0; i < parameterContexts.length; i++) { ParameterConstraintMappingContextImpl parameter = parameterContexts[i]; if (parameter != null) { constrainedParameters.add(parameter.build(constraintHelper, parameterNameProvider)); } else { constrainedParameters.add( new ConstrainedParameter( ConfigurationSource.API, ConstraintLocation.forParameter(executable, i), ReflectionHelper.typeOf(executable, i), i, executable.getParameterNames(parameterNameProvider).get(i))); } } return constrainedParameters; } }
/** @author Hardy Ferentschik */ public class XmlMappingParser { private static final Log log = LoggerFactory.make(); private static final String MESSAGE_PARAM = "message"; private static final String GROUPS_PARAM = "groups"; private static final String PAYLOAD_PARAM = "payload"; private static final String PACKAGE_SEPARATOR = "."; private final Set<Class<?>> processedClasses = newHashSet(); private final ConstraintHelper constraintHelper; private final AnnotationProcessingOptions annotationProcessingOptions; private final Map<Class<?>, Set<MetaConstraint<?>>> constraintMap; private final Map<Class<?>, List<Member>> cascadedMembers; private final Map<Class<?>, List<Class<?>>> defaultSequences; private final XmlParserHelper xmlParserHelper = new XmlParserHelper(); private static final ConcurrentMap<String, String> SCHEMAS_BY_VERSION = new ConcurrentHashMap<String, String>(2, 0.75f, 1); static { SCHEMAS_BY_VERSION.put("1.0", "META-INF/validation-mapping-1.0.xsd"); SCHEMAS_BY_VERSION.put("1.1", "META-INF/validation-mapping-1.1.xsd"); } public XmlMappingParser(ConstraintHelper constraintHelper) { this.constraintHelper = constraintHelper; this.annotationProcessingOptions = new AnnotationProcessingOptions(); this.constraintMap = newHashMap(); this.cascadedMembers = newHashMap(); this.defaultSequences = newHashMap(); } /** * Parses the given set of input stream representing XML constraint mappings. * * @param mappingStreams The streams to parse. Must support the mark/reset contract. */ public final void parse(Set<InputStream> mappingStreams) { try { JAXBContext jc = JAXBContext.newInstance(ConstraintMappingsType.class); for (InputStream in : mappingStreams) { String schemaVersion = xmlParserHelper.getSchemaVersion("constraint mapping file", in); String schemaResourceName = getSchemaResourceName(schemaVersion); Schema schema = xmlParserHelper.getSchema(schemaResourceName); Unmarshaller unmarshaller = jc.createUnmarshaller(); unmarshaller.setSchema(schema); ConstraintMappingsType mapping = getValidationConfig(in, unmarshaller); String defaultPackage = mapping.getDefaultPackage(); parseConstraintDefinitions(mapping.getConstraintDefinition(), defaultPackage); for (BeanType bean : mapping.getBean()) { Class<?> beanClass = getClass(bean.getClazz(), defaultPackage); checkClassHasNotBeenProcessed(processedClasses, beanClass); annotationProcessingOptions.ignoreAnnotationConstraintForClass( beanClass, bean.getIgnoreAnnotations()); parseClassLevelOverrides(bean.getClassType(), beanClass, defaultPackage); parseFieldLevelOverrides(bean.getField(), beanClass, defaultPackage); parsePropertyLevelOverrides(bean.getGetter(), beanClass, defaultPackage); processedClasses.add(beanClass); } } } catch (JAXBException e) { throw log.getErrorParsingMappingFileException(e); } } public final Set<Class<?>> getXmlConfiguredClasses() { return processedClasses; } public final AnnotationProcessingOptions getAnnotationProcessingOptions() { return annotationProcessingOptions; } public final <T> Set<MetaConstraint<?>> getConstraintsForClass(Class<T> beanClass) { Set<MetaConstraint<?>> theValue = constraintMap.get(beanClass); return theValue != null ? theValue : Collections.<MetaConstraint<?>>emptySet(); } public final List<Member> getCascadedMembersForClass(Class<?> beanClass) { if (cascadedMembers.containsKey(beanClass)) { return cascadedMembers.get(beanClass); } else { return Collections.emptyList(); } } public final List<Class<?>> getDefaultSequenceForClass(Class<?> beanClass) { return defaultSequences.get(beanClass); } @SuppressWarnings("unchecked") private void parseConstraintDefinitions( List<ConstraintDefinitionType> constraintDefinitionList, String defaultPackage) { for (ConstraintDefinitionType constraintDefinition : constraintDefinitionList) { String annotationClassName = constraintDefinition.getAnnotation(); Class<?> clazz = getClass(annotationClassName, defaultPackage); if (!clazz.isAnnotation()) { throw log.getIsNotAnAnnotationException(annotationClassName); } Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) clazz; ValidatedByType validatedByType = constraintDefinition.getValidatedBy(); List<Class<? extends ConstraintValidator<? extends Annotation, ?>>> constraintValidatorClasses = newArrayList(); if (validatedByType.getIncludeExistingValidators() != null && validatedByType.getIncludeExistingValidators()) { constraintValidatorClasses.addAll(findConstraintValidatorClasses(annotationClass)); } for (String validatorClassName : validatedByType.getValue()) { Class<? extends ConstraintValidator<?, ?>> validatorClass; validatorClass = (Class<? extends ConstraintValidator<?, ?>>) ReflectionHelper.loadClass(validatorClassName, this.getClass()); if (!ConstraintValidator.class.isAssignableFrom(validatorClass)) { throw log.getIsNotAConstraintValidatorClassException(validatorClass); } constraintValidatorClasses.add(validatorClass); } constraintHelper.addConstraintValidatorDefinition( annotationClass, constraintValidatorClasses); } } private List<Class<? extends ConstraintValidator<? extends Annotation, ?>>> findConstraintValidatorClasses(Class<? extends Annotation> annotationType) { List<Class<? extends ConstraintValidator<? extends Annotation, ?>>> constraintValidatorDefinitionClasses = newArrayList(); if (constraintHelper.isBuiltinConstraint(annotationType)) { constraintValidatorDefinitionClasses.addAll( constraintHelper.getBuiltInConstraints(annotationType)); } else { Class<? extends ConstraintValidator<?, ?>>[] validatedBy = annotationType.getAnnotation(Constraint.class).validatedBy(); constraintValidatorDefinitionClasses.addAll(Arrays.asList(validatedBy)); } return constraintValidatorDefinitionClasses; } private void checkClassHasNotBeenProcessed(Set<Class<?>> processedClasses, Class<?> beanClass) { if (processedClasses.contains(beanClass)) { throw log.getBeanClassHasAlreadyBeConfiguredInXmlException(beanClass.getName()); } } private void parseFieldLevelOverrides( List<FieldType> fields, Class<?> beanClass, String defaultPackage) { List<String> fieldNames = newArrayList(); for (FieldType fieldType : fields) { String fieldName = fieldType.getName(); if (fieldNames.contains(fieldName)) { throw log.getIsDefinedTwiceInMappingXmlForBeanException(fieldName, beanClass.getName()); } else { fieldNames.add(fieldName); } final boolean containsField = ReflectionHelper.containsDeclaredField(beanClass, fieldName); if (!containsField) { throw log.getBeanDoesNotContainTheFieldException(beanClass.getName(), fieldName); } final Field field = ReflectionHelper.getDeclaredField(beanClass, fieldName); // ignore annotations boolean ignoreFieldAnnotation = fieldType.getIgnoreAnnotations() == null ? false : fieldType.getIgnoreAnnotations(); if (ignoreFieldAnnotation) { annotationProcessingOptions.ignorePropertyLevelConstraintAnnotationsOnMember(field); } // valid if (fieldType.getValid() != null) { addCascadedMember(beanClass, field); } // constraints for (ConstraintType constraint : fieldType.getConstraint()) { MetaConstraint<?> metaConstraint = createMetaConstraint(constraint, beanClass, field, defaultPackage); addMetaConstraint(beanClass, metaConstraint); } } } private void parsePropertyLevelOverrides( List<GetterType> getters, Class<?> beanClass, String defaultPackage) { List<String> getterNames = newArrayList(); for (GetterType getterType : getters) { String getterName = getterType.getName(); if (getterNames.contains(getterName)) { throw log.getIsDefinedTwiceInMappingXmlForBeanException(getterName, beanClass.getName()); } else { getterNames.add(getterName); } boolean containsMethod = ReflectionHelper.containsMethodWithPropertyName(beanClass, getterName); if (!containsMethod) { throw log.getBeanDoesNotContainThePropertyException(beanClass.getName(), getterName); } final Method method = ReflectionHelper.getMethodFromPropertyName(beanClass, getterName); // ignore annotations boolean ignoreGetterAnnotation = getterType.getIgnoreAnnotations() == null ? false : getterType.getIgnoreAnnotations(); if (ignoreGetterAnnotation) { annotationProcessingOptions.ignorePropertyLevelConstraintAnnotationsOnMember(method); } // valid if (getterType.getValid() != null) { addCascadedMember(beanClass, method); } // constraints for (ConstraintType constraint : getterType.getConstraint()) { MetaConstraint<?> metaConstraint = createMetaConstraint(constraint, beanClass, method, defaultPackage); addMetaConstraint(beanClass, metaConstraint); } } } private void parseClassLevelOverrides( ClassType classType, Class<?> beanClass, String defaultPackage) { if (classType == null) { return; } // ignore annotation if (classType.getIgnoreAnnotations() != null) { annotationProcessingOptions.ignoreClassLevelConstraintAnnotations( beanClass, classType.getIgnoreAnnotations()); } // group sequence List<Class<?>> groupSequence = createGroupSequence(classType.getGroupSequence(), defaultPackage); if (!groupSequence.isEmpty()) { defaultSequences.put(beanClass, groupSequence); } // constraints for (ConstraintType constraint : classType.getConstraint()) { MetaConstraint<?> metaConstraint = createMetaConstraint(constraint, beanClass, null, defaultPackage); addMetaConstraint(beanClass, metaConstraint); } } private void addMetaConstraint(Class<?> beanClass, MetaConstraint<?> metaConstraint) { if (constraintMap.containsKey(beanClass)) { constraintMap.get(beanClass).add(metaConstraint); } else { Set<MetaConstraint<?>> constraintList = newHashSet(); constraintList.add(metaConstraint); constraintMap.put(beanClass, constraintList); } } private void addCascadedMember(Class<?> beanClass, Member member) { if (cascadedMembers.containsKey(beanClass)) { cascadedMembers.get(beanClass).add(member); } else { List<Member> tmpList = newArrayList(); tmpList.add(member); cascadedMembers.put(beanClass, tmpList); } } private List<Class<?>> createGroupSequence( GroupSequenceType groupSequenceType, String defaultPackage) { List<Class<?>> groupSequence = newArrayList(); if (groupSequenceType != null) { for (String groupName : groupSequenceType.getValue()) { Class<?> group = getClass(groupName, defaultPackage); groupSequence.add(group); } } return groupSequence; } private <A extends Annotation, T> MetaConstraint<?> createMetaConstraint( ConstraintType constraint, Class<T> beanClass, Member member, String defaultPackage) { @SuppressWarnings("unchecked") Class<A> annotationClass = (Class<A>) getClass(constraint.getAnnotation(), defaultPackage); AnnotationDescriptor<A> annotationDescriptor = new AnnotationDescriptor<A>(annotationClass); if (constraint.getMessage() != null) { annotationDescriptor.setValue(MESSAGE_PARAM, constraint.getMessage()); } annotationDescriptor.setValue(GROUPS_PARAM, getGroups(constraint.getGroups(), defaultPackage)); annotationDescriptor.setValue( PAYLOAD_PARAM, getPayload(constraint.getPayload(), defaultPackage)); for (ElementType elementType : constraint.getElement()) { String name = elementType.getName(); checkNameIsValid(name); Class<?> returnType = getAnnotationParameterType(annotationClass, name); Object elementValue = getElementValue(elementType, returnType); annotationDescriptor.setValue(name, elementValue); } A annotation; try { annotation = AnnotationFactory.create(annotationDescriptor); } catch (RuntimeException e) { throw log.getUnableToCreateAnnotationForConfiguredConstraintException(e); } java.lang.annotation.ElementType type = java.lang.annotation.ElementType.TYPE; if (member instanceof Method) { type = java.lang.annotation.ElementType.METHOD; } else if (member instanceof Field) { type = java.lang.annotation.ElementType.FIELD; } // we set initially ConstraintOrigin.DEFINED_LOCALLY for all xml configured constraints // later we will make copies of this constraint descriptor when needed and adjust the // ConstraintOrigin ConstraintDescriptorImpl<A> constraintDescriptor = new ConstraintDescriptorImpl<A>( annotation, constraintHelper, type, ConstraintOrigin.DEFINED_LOCALLY); return new MetaConstraint<A>( constraintDescriptor, new BeanConstraintLocation(beanClass, member)); } private <A extends Annotation> Class<?> getAnnotationParameterType( Class<A> annotationClass, String name) { Method m = ReflectionHelper.getMethod(annotationClass, name); if (m == null) { throw log.getAnnotationDoesNotContainAParameterException(annotationClass.getName(), name); } return m.getReturnType(); } private Object getElementValue(ElementType elementType, Class<?> returnType) { removeEmptyContentElements(elementType); boolean isArray = returnType.isArray(); if (!isArray) { if (elementType.getContent().size() != 1) { throw log.getAttemptToSpecifyAnArrayWhereSingleValueIsExpectedException(); } return getSingleValue(elementType.getContent().get(0), returnType); } else { List<Object> values = newArrayList(); for (Serializable s : elementType.getContent()) { values.add(getSingleValue(s, returnType.getComponentType())); } return values.toArray( (Object[]) Array.newInstance(returnType.getComponentType(), values.size())); } } private void removeEmptyContentElements(ElementType elementType) { List<Serializable> contentToDelete = newArrayList(); for (Serializable content : elementType.getContent()) { if (content instanceof String && ((String) content).matches("[\\n ].*")) { contentToDelete.add(content); } } elementType.getContent().removeAll(contentToDelete); } private Object getSingleValue(Serializable serializable, Class<?> returnType) { Object returnValue; if (serializable instanceof String) { String value = (String) serializable; returnValue = convertStringToReturnType(returnType, value); } else if (serializable instanceof JAXBElement && ((JAXBElement<?>) serializable).getDeclaredType().equals(String.class)) { JAXBElement<?> elem = (JAXBElement<?>) serializable; String value = (String) elem.getValue(); returnValue = convertStringToReturnType(returnType, value); } else if (serializable instanceof JAXBElement && ((JAXBElement<?>) serializable).getDeclaredType().equals(AnnotationType.class)) { JAXBElement<?> elem = (JAXBElement<?>) serializable; AnnotationType annotationType = (AnnotationType) elem.getValue(); try { @SuppressWarnings("unchecked") Class<Annotation> annotationClass = (Class<Annotation>) returnType; returnValue = createAnnotation(annotationType, annotationClass); } catch (ClassCastException e) { throw log.getUnexpectedParameterValueException(e); } } else { throw log.getUnexpectedParameterValueException(); } return returnValue; } private <A extends Annotation> Annotation createAnnotation( AnnotationType annotationType, Class<A> returnType) { AnnotationDescriptor<A> annotationDescriptor = new AnnotationDescriptor<A>(returnType); for (ElementType elementType : annotationType.getElement()) { String name = elementType.getName(); Class<?> parameterType = getAnnotationParameterType(returnType, name); Object elementValue = getElementValue(elementType, parameterType); annotationDescriptor.setValue(name, elementValue); } return AnnotationFactory.create(annotationDescriptor); } private Object convertStringToReturnType(Class<?> returnType, String value) { Object returnValue; if (returnType.getName().equals(byte.class.getName())) { try { returnValue = Byte.parseByte(value); } catch (NumberFormatException e) { throw log.getInvalidNumberFormatException("byte", e); } } else if (returnType.getName().equals(short.class.getName())) { try { returnValue = Short.parseShort(value); } catch (NumberFormatException e) { throw log.getInvalidNumberFormatException("short", e); } } else if (returnType.getName().equals(int.class.getName())) { try { returnValue = Integer.parseInt(value); } catch (NumberFormatException e) { throw log.getInvalidNumberFormatException("int", e); } } else if (returnType.getName().equals(long.class.getName())) { try { returnValue = Long.parseLong(value); } catch (NumberFormatException e) { throw log.getInvalidNumberFormatException("long", e); } } else if (returnType.getName().equals(float.class.getName())) { try { returnValue = Float.parseFloat(value); } catch (NumberFormatException e) { throw log.getInvalidNumberFormatException("float", e); } } else if (returnType.getName().equals(double.class.getName())) { try { returnValue = Double.parseDouble(value); } catch (NumberFormatException e) { throw log.getInvalidNumberFormatException("double", e); } } else if (returnType.getName().equals(boolean.class.getName())) { returnValue = Boolean.parseBoolean(value); } else if (returnType.getName().equals(char.class.getName())) { if (value.length() != 1) { throw log.getInvalidCharValueException(value); } returnValue = value.charAt(0); } else if (returnType.getName().equals(String.class.getName())) { returnValue = value; } else if (returnType.getName().equals(Class.class.getName())) { returnValue = ReflectionHelper.loadClass(value, this.getClass()); } else { try { @SuppressWarnings("unchecked") Class<Enum> enumClass = (Class<Enum>) returnType; returnValue = Enum.valueOf(enumClass, value); } catch (ClassCastException e) { throw log.getInvalidReturnTypeException(returnType, e); } } return returnValue; } private void checkNameIsValid(String name) { if (MESSAGE_PARAM.equals(name) || GROUPS_PARAM.equals(name)) { throw log.getReservedParameterNamesException(MESSAGE_PARAM, GROUPS_PARAM, PAYLOAD_PARAM); } } private Class<?>[] getGroups(GroupsType groupsType, String defaultPackage) { if (groupsType == null) { return new Class[] {}; } List<Class<?>> groupList = newArrayList(); for (String groupClass : groupsType.getValue()) { groupList.add(getClass(groupClass, defaultPackage)); } return groupList.toArray(new Class[groupList.size()]); } @SuppressWarnings("unchecked") private Class<? extends Payload>[] getPayload(PayloadType payloadType, String defaultPackage) { if (payloadType == null) { return new Class[] {}; } List<Class<? extends Payload>> payloadList = newArrayList(); for (String groupClass : payloadType.getValue()) { Class<?> payload = getClass(groupClass, defaultPackage); if (!Payload.class.isAssignableFrom(payload)) { throw log.getWrongPayloadClassException(payload.getName()); } else { payloadList.add((Class<? extends Payload>) payload); } } return payloadList.toArray(new Class[payloadList.size()]); } private Class<?> getClass(String clazz, String defaultPackage) { String fullyQualifiedClass; if (isQualifiedClass(clazz)) { fullyQualifiedClass = clazz; } else { fullyQualifiedClass = defaultPackage + PACKAGE_SEPARATOR + clazz; } return ReflectionHelper.loadClass(fullyQualifiedClass, this.getClass()); } private boolean isQualifiedClass(String clazz) { return clazz.contains(PACKAGE_SEPARATOR); } private ConstraintMappingsType getValidationConfig(InputStream in, Unmarshaller unmarshaller) { ConstraintMappingsType constraintMappings; try { // check whether mark is supported, if so we can reset the stream in order to allow reuse of // Configuration boolean markSupported = in.markSupported(); if (markSupported) { in.mark(Integer.MAX_VALUE); } StreamSource stream = new StreamSource(new CloseIgnoringInputStream(in)); JAXBElement<ConstraintMappingsType> root = unmarshaller.unmarshal(stream, ConstraintMappingsType.class); constraintMappings = root.getValue(); if (markSupported) { try { in.reset(); } catch (IOException e) { log.debug("Unable to reset input stream."); } } } catch (JAXBException e) { throw log.getErrorParsingMappingFileException(e); } return constraintMappings; } private String getSchemaResourceName(String schemaVersion) { String schemaResource = SCHEMAS_BY_VERSION.get(schemaVersion); if (schemaResource == null) { throw log.getUnsupportedSchemaVersionException("constraint mapping file", schemaVersion); } return schemaResource; } // JAXB closes the underlying input stream private static class CloseIgnoringInputStream extends FilterInputStream { public CloseIgnoringInputStream(InputStream in) { super(in); } @Override public void close() { // do nothing } } }
/** * This class encapsulates all meta data needed for validation. Implementations of {@code Validator} * interface can instantiate an instance of this class and delegate the metadata extraction to it. * * @author Hardy Ferentschik * @author Gunnar Morling * @author Kevin Pollet <*****@*****.**> (C) 2011 SERLI */ public final class BeanMetaDataImpl<T> implements BeanMetaData<T> { private static final Log log = LoggerFactory.make(); /** The root bean class for this meta data. */ private final Class<T> beanClass; /** * Set of all constraints for this bean type (defined on any implemented interfaces or super * types) */ private final Set<MetaConstraint<?>> allMetaConstraints; /** * Set of all constraints which are directly defined on the bean or any of the directly * implemented interfaces */ private final Set<MetaConstraint<?>> directMetaConstraints; /** * Contains constrained related meta data for all methods and constructors of the type represented * by this bean meta data. Keyed by executable, values are an aggregated view on each executable * together with all the executables from the inheritance hierarchy with the same signature. */ private final Map<String, ExecutableMetaData> executableMetaDataMap; /** Property meta data keyed against the property name */ private final Map<String, PropertyMetaData> propertyMetaDataMap; /** The cascaded properties of this bean. */ private final Set<Cascadable> cascadedProperties; /** The bean descriptor for this bean. */ private final BeanDescriptor beanDescriptor; /** The default groups sequence for this bean class. */ private List<Class<?>> defaultGroupSequence = newArrayList(); /** * The default group sequence provider. * * @see org.hibernate.validator.group.GroupSequenceProvider * @see DefaultGroupSequenceProvider */ private DefaultGroupSequenceProvider<? super T> defaultGroupSequenceProvider; /** * The class hierarchy for this class starting with the class itself going up the inheritance * chain. Interfaces are not included. */ private final List<Class<? super T>> classHierarchyWithoutInterfaces; /** * Creates a new {@link BeanMetaDataImpl} * * @param beanClass The Java type represented by this meta data object. * @param defaultGroupSequence The default group sequence. * @param defaultGroupSequenceProvider The default group sequence provider if set. * @param constraintMetaDataSet All constraint meta data relating to the represented type. */ public BeanMetaDataImpl( Class<T> beanClass, List<Class<?>> defaultGroupSequence, DefaultGroupSequenceProvider<? super T> defaultGroupSequenceProvider, Set<ConstraintMetaData> constraintMetaDataSet) { this.beanClass = beanClass; this.propertyMetaDataMap = newHashMap(); Set<PropertyMetaData> propertyMetaDataSet = newHashSet(); Set<ExecutableMetaData> executableMetaDataSet = newHashSet(); for (ConstraintMetaData constraintMetaData : constraintMetaDataSet) { if (constraintMetaData.getKind() == ElementKind.PROPERTY) { propertyMetaDataSet.add((PropertyMetaData) constraintMetaData); } else { executableMetaDataSet.add((ExecutableMetaData) constraintMetaData); } } Set<Cascadable> cascadedProperties = newHashSet(); Set<MetaConstraint<?>> allMetaConstraints = newHashSet(); for (PropertyMetaData propertyMetaData : propertyMetaDataSet) { propertyMetaDataMap.put(propertyMetaData.getName(), propertyMetaData); if (propertyMetaData.isCascading()) { cascadedProperties.add(propertyMetaData); } allMetaConstraints.addAll(propertyMetaData.getConstraints()); } this.cascadedProperties = Collections.unmodifiableSet(cascadedProperties); this.allMetaConstraints = Collections.unmodifiableSet(allMetaConstraints); this.classHierarchyWithoutInterfaces = ClassHierarchyHelper.getHierarchy(beanClass, Filters.excludeInterfaces()); setDefaultGroupSequenceOrProvider(defaultGroupSequence, defaultGroupSequenceProvider); this.directMetaConstraints = getDirectConstraints(); this.executableMetaDataMap = Collections.unmodifiableMap(byIdentifier(executableMetaDataSet)); this.beanDescriptor = new BeanDescriptorImpl( beanClass, getClassLevelConstraintsAsDescriptors(), getConstrainedPropertiesAsDescriptors(), getConstrainedMethodsAsDescriptors(), getConstrainedConstructorsAsDescriptors(), defaultGroupSequenceIsRedefined(), getDefaultGroupSequence(null)); } @Override public Class<T> getBeanClass() { return beanClass; } @Override public boolean hasConstraints() { if (beanDescriptor.isBeanConstrained() || !beanDescriptor.getConstrainedConstructors().isEmpty() || !beanDescriptor .getConstrainedMethods(MethodType.NON_GETTER, MethodType.GETTER) .isEmpty()) { return true; } return false; } @Override public BeanDescriptor getBeanDescriptor() { return beanDescriptor; } @Override public Set<Cascadable> getCascadables() { return cascadedProperties; } @Override public PropertyMetaData getMetaDataFor(String propertyName) { return propertyMetaDataMap.get(propertyName); } @Override public Set<MetaConstraint<?>> getMetaConstraints() { return allMetaConstraints; } @Override public Set<MetaConstraint<?>> getDirectMetaConstraints() { return directMetaConstraints; } @Override public ExecutableMetaData getMetaDataFor(ExecutableElement executable) { return executableMetaDataMap.get(executable.getIdentifier()); } @Override public List<Class<?>> getDefaultGroupSequence(T beanState) { if (hasDefaultGroupSequenceProvider()) { List<Class<?>> providerDefaultGroupSequence = defaultGroupSequenceProvider.getValidationGroups(beanState); return getValidDefaultGroupSequence(providerDefaultGroupSequence); } return Collections.unmodifiableList(defaultGroupSequence); } @Override public boolean defaultGroupSequenceIsRedefined() { return defaultGroupSequence.size() > 1 || hasDefaultGroupSequenceProvider(); } @Override public List<Class<? super T>> getClassHierarchy() { return classHierarchyWithoutInterfaces; } private Set<ConstraintDescriptorImpl<?>> getClassLevelConstraintsAsDescriptors() { Set<MetaConstraint<?>> classLevelConstraints = getClassLevelConstraints(allMetaConstraints); Set<ConstraintDescriptorImpl<?>> theValue = newHashSet(); for (MetaConstraint<?> metaConstraint : classLevelConstraints) { theValue.add(metaConstraint.getDescriptor()); } return theValue; } private Map<String, PropertyDescriptor> getConstrainedPropertiesAsDescriptors() { Map<String, PropertyDescriptor> theValue = newHashMap(); for (Entry<String, PropertyMetaData> entry : propertyMetaDataMap.entrySet()) { if (entry.getValue().isConstrained() && entry.getValue().getName() != null) { theValue.put( entry.getKey(), entry .getValue() .asDescriptor(defaultGroupSequenceIsRedefined(), getDefaultGroupSequence(null))); } } return theValue; } private Map<String, ExecutableDescriptorImpl> getConstrainedMethodsAsDescriptors() { Map<String, ExecutableDescriptorImpl> constrainedMethodDescriptors = newHashMap(); for (ExecutableMetaData executableMetaData : executableMetaDataMap.values()) { if (executableMetaData.getKind() == ElementKind.METHOD && executableMetaData.isConstrained()) { constrainedMethodDescriptors.put( executableMetaData.getIdentifier(), executableMetaData.asDescriptor( defaultGroupSequenceIsRedefined(), getDefaultGroupSequence(null))); } } return constrainedMethodDescriptors; } private Map<String, ConstructorDescriptor> getConstrainedConstructorsAsDescriptors() { Map<String, ConstructorDescriptor> constrainedMethodDescriptors = newHashMap(); for (ExecutableMetaData executableMetaData : executableMetaDataMap.values()) { if (executableMetaData.getKind() == ElementKind.CONSTRUCTOR && executableMetaData.isConstrained()) { constrainedMethodDescriptors.put( executableMetaData.getIdentifier(), executableMetaData.asDescriptor( defaultGroupSequenceIsRedefined(), getDefaultGroupSequence(null))); } } return constrainedMethodDescriptors; } private void setDefaultGroupSequenceOrProvider( List<Class<?>> defaultGroupSequence, DefaultGroupSequenceProvider<? super T> defaultGroupSequenceProvider) { if (defaultGroupSequence != null && defaultGroupSequenceProvider != null) { throw log.getInvalidDefaultGroupSequenceDefinitionException(); } if (defaultGroupSequenceProvider != null) { this.defaultGroupSequenceProvider = defaultGroupSequenceProvider; } else if (defaultGroupSequence != null && !defaultGroupSequence.isEmpty()) { setDefaultGroupSequence(defaultGroupSequence); } else { setDefaultGroupSequence(Arrays.<Class<?>>asList(beanClass)); } } private Set<MetaConstraint<?>> getClassLevelConstraints(Set<MetaConstraint<?>> constraints) { Set<MetaConstraint<?>> classLevelConstraints = partition(constraints, byElementType()).get(ElementType.TYPE); return classLevelConstraints != null ? classLevelConstraints : Collections.<MetaConstraint<?>>emptySet(); } private Set<MetaConstraint<?>> getDirectConstraints() { Set<MetaConstraint<?>> constraints = newHashSet(); Set<Class<?>> classAndInterfaces = newHashSet(); classAndInterfaces.add(beanClass); classAndInterfaces.addAll(ClassHierarchyHelper.getDirectlyImplementedInterfaces(beanClass)); for (Class<?> clazz : classAndInterfaces) { for (MetaConstraint<?> metaConstraint : allMetaConstraints) { if (metaConstraint.getLocation().getDeclaringClass().equals(clazz)) { constraints.add(metaConstraint); } } } return Collections.unmodifiableSet(constraints); } /** Builds up the method meta data for this type */ private Map<String, ExecutableMetaData> byIdentifier(Set<ExecutableMetaData> executables) { Map<String, ExecutableMetaData> theValue = newHashMap(); for (ExecutableMetaData executableMetaData : executables) { theValue.put(executableMetaData.getIdentifier(), executableMetaData); } return theValue; } private void setDefaultGroupSequence(List<Class<?>> groupSequence) { defaultGroupSequence = getValidDefaultGroupSequence(groupSequence); } private List<Class<?>> getValidDefaultGroupSequence(List<Class<?>> groupSequence) { List<Class<?>> validDefaultGroupSequence = new ArrayList<Class<?>>(); boolean groupSequenceContainsDefault = false; if (groupSequence != null) { for (Class<?> group : groupSequence) { if (group.getName().equals(beanClass.getName())) { validDefaultGroupSequence.add(Default.class); groupSequenceContainsDefault = true; } else if (group.getName().equals(Default.class.getName())) { throw log.getNoDefaultGroupInGroupSequenceException(); } else { validDefaultGroupSequence.add(group); } } } if (!groupSequenceContainsDefault) { throw log.getBeanClassMustBePartOfRedefinedDefaultGroupSequenceException(beanClass.getName()); } if (log.isTraceEnabled()) { log.tracef( "Members of the default group sequence for bean %s are: %s.", beanClass.getName(), validDefaultGroupSequence); } return validDefaultGroupSequence; } private boolean hasDefaultGroupSequenceProvider() { return defaultGroupSequenceProvider != null; } private Partitioner<ElementType, MetaConstraint<?>> byElementType() { return new Partitioner<ElementType, MetaConstraint<?>>() { @Override public ElementType getPartition(MetaConstraint<?> constraint) { return constraint.getElementType(); } }; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("BeanMetaDataImpl"); sb.append("{beanClass=").append(beanClass.getSimpleName()); sb.append(", constraintCount=").append(getMetaConstraints().size()); sb.append(", cascadedPropertiesCount=").append(cascadedProperties.size()); sb.append(", defaultGroupSequence=").append(getDefaultGroupSequence(null)); sb.append('}'); return sb.toString(); } public static class BeanMetaDataBuilder<T> { private final ConstraintHelper constraintHelper; private final Class<T> beanClass; private final Set<BuilderDelegate> builders = newHashSet(); private final ExecutableHelper executableHelper; private ConfigurationSource sequenceSource; private ConfigurationSource providerSource; private List<Class<?>> defaultGroupSequence; private DefaultGroupSequenceProvider<? super T> defaultGroupSequenceProvider; private BeanMetaDataBuilder( ConstraintHelper constraintHelper, ExecutableHelper executableHelper, Class<T> beanClass) { this.beanClass = beanClass; this.constraintHelper = constraintHelper; this.executableHelper = executableHelper; } public static <T> BeanMetaDataBuilder<T> getInstance( ConstraintHelper constraintHelper, ExecutableHelper executableHelper, Class<T> beanClass) { return new BeanMetaDataBuilder<T>(constraintHelper, executableHelper, beanClass); } public void add(BeanConfiguration<? super T> configuration) { if (configuration.getBeanClass().equals(beanClass)) { if (configuration.getDefaultGroupSequence() != null && (sequenceSource == null || configuration.getSource().getPriority() >= sequenceSource.getPriority())) { sequenceSource = configuration.getSource(); defaultGroupSequence = configuration.getDefaultGroupSequence(); } if (configuration.getDefaultGroupSequenceProvider() != null && (providerSource == null || configuration.getSource().getPriority() >= providerSource.getPriority())) { providerSource = configuration.getSource(); defaultGroupSequenceProvider = configuration.getDefaultGroupSequenceProvider(); } } for (ConstrainedElement constrainedElement : configuration.getConstrainedElements()) { addMetaDataToBuilder(constrainedElement, builders); } } private void addMetaDataToBuilder( ConstrainedElement constrainableElement, Set<BuilderDelegate> builders) { for (BuilderDelegate builder : builders) { boolean foundBuilder = builder.add(constrainableElement); if (foundBuilder) { return; } } builders.add( new BuilderDelegate(beanClass, constrainableElement, constraintHelper, executableHelper)); } public BeanMetaDataImpl<T> build() { Set<ConstraintMetaData> aggregatedElements = newHashSet(); for (BuilderDelegate builder : builders) { aggregatedElements.addAll(builder.build()); } return new BeanMetaDataImpl<T>( beanClass, defaultGroupSequence, defaultGroupSequenceProvider, aggregatedElements); } } private static class BuilderDelegate { private final Class<?> beanClass; private final ConstraintHelper constraintHelper; private final ExecutableHelper executableHelper; private MetaDataBuilder propertyBuilder; private ExecutableMetaData.Builder methodBuilder; public BuilderDelegate( Class<?> beanClass, ConstrainedElement constrainedElement, ConstraintHelper constraintHelper, ExecutableHelper executableHelper) { this.beanClass = beanClass; this.constraintHelper = constraintHelper; this.executableHelper = executableHelper; switch (constrainedElement.getKind()) { case FIELD: ConstrainedField constrainedField = (ConstrainedField) constrainedElement; propertyBuilder = new PropertyMetaData.Builder(beanClass, constrainedField, constraintHelper); break; case CONSTRUCTOR: case METHOD: ConstrainedExecutable constrainedExecutable = (ConstrainedExecutable) constrainedElement; methodBuilder = new ExecutableMetaData.Builder( beanClass, constrainedExecutable, constraintHelper, executableHelper); if (constrainedExecutable.isGetterMethod()) { propertyBuilder = new PropertyMetaData.Builder(beanClass, constrainedExecutable, constraintHelper); } break; case TYPE: ConstrainedType constrainedType = (ConstrainedType) constrainedElement; propertyBuilder = new PropertyMetaData.Builder(beanClass, constrainedType, constraintHelper); break; } } public boolean add(ConstrainedElement constrainedElement) { boolean added = false; if (methodBuilder != null && methodBuilder.accepts(constrainedElement)) { methodBuilder.add(constrainedElement); added = true; } if (propertyBuilder != null && propertyBuilder.accepts(constrainedElement)) { propertyBuilder.add(constrainedElement); if (!added && constrainedElement.getKind() == ConstrainedElementKind.METHOD && methodBuilder == null) { ConstrainedExecutable constrainedMethod = (ConstrainedExecutable) constrainedElement; methodBuilder = new ExecutableMetaData.Builder( beanClass, constrainedMethod, constraintHelper, executableHelper); } added = true; } return added; } public Set<ConstraintMetaData> build() { Set<ConstraintMetaData> metaDataSet = newHashSet(); if (propertyBuilder != null) { metaDataSet.add(propertyBuilder.build()); } if (methodBuilder != null) { metaDataSet.add(methodBuilder.build()); } return metaDataSet; } } }
/** * A message interpolator which can interpolate the validated value and format this value using the * syntax from {@link java.util.Formatter}. Check the <code>Formatter</code> documentation for * formatting syntax and options. If no formatting string is specified <code> * String.valueOf(validatedValue)}</code> is called. * * <p>To interpolate the validated value add <code>{validatedValue}</code> into the * message. To specify a format pattern use <code>${validatedValue:[format string]}</code> * , e.g. <code>${validatedValue:%1$ty}</code>. * * @author Hardy Ferentschik */ public class ValueFormatterMessageInterpolator implements MessageInterpolator { private static final Log log = LoggerFactory.make(); public static final String VALIDATED_VALUE_KEYWORD = "validatedValue"; public static final String VALIDATED_VALUE_FORMAT_SEPARATOR = ":"; private static final Pattern VALIDATED_VALUE_START_PATTERN = Pattern.compile("\\$\\{" + VALIDATED_VALUE_KEYWORD); private final MessageInterpolator delegate; private final Locale defaultLocale; public ValueFormatterMessageInterpolator() { this(null); } public ValueFormatterMessageInterpolator(MessageInterpolator userMessageInterpolator) { defaultLocale = Locale.getDefault(); if (userMessageInterpolator == null) { this.delegate = new ResourceBundleMessageInterpolator(); } else { this.delegate = userMessageInterpolator; } } public String interpolate(String message, Context context) { return interpolate(message, context, defaultLocale); } public String interpolate(String message, Context context, Locale locale) { String tmp = delegate.interpolate(message, context, locale); return interpolateMessage(tmp, context.getValidatedValue(), locale); } /** * Interpolate the validated value in the given message. * * @param message the message where validated value have to be interpolated * @param validatedValue the value of the object being validated * @param locale the {@code Locale} to use for message interpolation * @return the interpolated message */ private String interpolateMessage(String message, Object validatedValue, Locale locale) { String interpolatedMessage = message; Matcher matcher = VALIDATED_VALUE_START_PATTERN.matcher(message); while (matcher.find()) { int nbOpenCurlyBrace = 1; boolean isDoubleQuoteBloc = false; boolean isSimpleQuoteBloc = false; int lastIndex = matcher.end(); do { char current = message.charAt(lastIndex); if (current == '\'') { if (!isDoubleQuoteBloc && !isEscaped(message, lastIndex)) { isSimpleQuoteBloc = !isSimpleQuoteBloc; } } else if (current == '"') { if (!isSimpleQuoteBloc && !isEscaped(message, lastIndex)) { isDoubleQuoteBloc = !isDoubleQuoteBloc; } } else if (!isDoubleQuoteBloc && !isSimpleQuoteBloc) { if (current == '{') { nbOpenCurlyBrace++; } else if (current == '}') { nbOpenCurlyBrace--; } } lastIndex++; } while (nbOpenCurlyBrace > 0 && lastIndex < message.length()); // The validated value expression seems correct if (nbOpenCurlyBrace == 0) { String expression = message.substring(matcher.start(), lastIndex); String expressionValue = interpolateValidatedValue(expression, validatedValue, locale); interpolatedMessage = interpolatedMessage.replaceFirst( Pattern.quote(expression), Matcher.quoteReplacement(expressionValue)); } } return interpolatedMessage; } /** * Returns if the character at the given index in the String is an escaped character (preceded by * a backslash character). * * @param enclosingString the string which contain the given character * @param charIndex the index of the character * @return true if the given character is escaped false otherwise */ private boolean isEscaped(String enclosingString, int charIndex) { if (charIndex < 0 || charIndex > enclosingString.length()) { throw log.getInvalidIndexException("0", "enclosingString.length() - 1"); } return charIndex > 0 && enclosingString.charAt(charIndex - 1) == '\\'; } /** * Returns the value of the interpolated validated value. * * @param expression the expression to interpolate * @param validatedValue the value of the object being validated * @param locale the {@code Locale} to be used * @return the interpolated value */ private String interpolateValidatedValue( String expression, Object validatedValue, Locale locale) { String interpolatedValue; int separatorIndex = expression.indexOf(VALIDATED_VALUE_FORMAT_SEPARATOR); if (separatorIndex == -1) { interpolatedValue = String.valueOf(validatedValue); } else { String format = expression.substring(separatorIndex + 1, expression.length() - 1); if (format.length() == 0) { throw log.getMissingFormatStringInTemplateException(expression); } try { interpolatedValue = String.format(locale, format, validatedValue); } catch (IllegalFormatException e) { throw log.throwInvalidFormat(e.getMessage(), e); } } return interpolatedValue; } }
/** * Provides utility methods for working with types. * * @author Mark Hobson * @author Hardy Ferentschik */ public final class TypeHelper { private static final Map<Class<?>, Set<Class<?>>> SUBTYPES_BY_PRIMITIVE; private static final int VALIDATOR_TYPE_INDEX = 1; private static final Log log = LoggerFactory.make(); static { Map<Class<?>, Set<Class<?>>> subtypesByPrimitive = newHashMap(); putPrimitiveSubtypes(subtypesByPrimitive, Void.TYPE); putPrimitiveSubtypes(subtypesByPrimitive, Boolean.TYPE); putPrimitiveSubtypes(subtypesByPrimitive, Byte.TYPE); putPrimitiveSubtypes(subtypesByPrimitive, Character.TYPE); putPrimitiveSubtypes(subtypesByPrimitive, Short.TYPE, Byte.TYPE); putPrimitiveSubtypes(subtypesByPrimitive, Integer.TYPE, Character.TYPE, Short.TYPE); putPrimitiveSubtypes(subtypesByPrimitive, Long.TYPE, Integer.TYPE); putPrimitiveSubtypes(subtypesByPrimitive, Float.TYPE, Long.TYPE); putPrimitiveSubtypes(subtypesByPrimitive, Double.TYPE, Float.TYPE); SUBTYPES_BY_PRIMITIVE = Collections.unmodifiableMap(subtypesByPrimitive); } private TypeHelper() { throw new AssertionError(); } public static boolean isAssignable(Type supertype, Type type) { Contracts.assertNotNull(supertype, "supertype"); Contracts.assertNotNull(type, "type"); if (supertype.equals(type)) { return true; } if (supertype instanceof Class<?>) { if (type instanceof Class<?>) { return isClassAssignable((Class<?>) supertype, (Class<?>) type); } if (type instanceof ParameterizedType) { return isAssignable(supertype, ((ParameterizedType) type).getRawType()); } if (type instanceof TypeVariable<?>) { return isTypeVariableAssignable(supertype, (TypeVariable<?>) type); } if (type instanceof GenericArrayType) { if (((Class<?>) supertype).isArray()) { return isAssignable(getComponentType(supertype), getComponentType(type)); } return isArraySupertype((Class<?>) supertype); } if (type instanceof WildcardType) { return isClassAssignableToWildcardType((Class<?>) supertype, (WildcardType) type); } return false; } if (supertype instanceof ParameterizedType) { if (type instanceof Class<?>) { return isSuperAssignable(supertype, type); } if (type instanceof ParameterizedType) { return isParameterizedTypeAssignable( (ParameterizedType) supertype, (ParameterizedType) type); } return false; } if (type instanceof TypeVariable<?>) { return isTypeVariableAssignable(supertype, (TypeVariable<?>) type); } if (supertype instanceof GenericArrayType) { if (isArray(type)) { return isAssignable(getComponentType(supertype), getComponentType(type)); } return false; } if (supertype instanceof WildcardType) { return isWildcardTypeAssignable((WildcardType) supertype, type); } return false; } /** * Gets the erased type of the specified type. * * @param type the type to perform erasure on * @return the erased type, never a parameterized type nor a type variable * @see <a href="http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.6">4.6 * Type Erasure</a> */ public static Type getErasedType(Type type) { // the erasure of a parameterized type G<T1, ... ,Tn> is |G| if (type instanceof ParameterizedType) { Type rawType = ((ParameterizedType) type).getRawType(); return getErasedType(rawType); } // TODO: the erasure of a nested type T.C is |T|.C // the erasure of an array type T[] is |T|[] if (isArray(type)) { Type componentType = getComponentType(type); Type erasedComponentType = getErasedType(componentType); return getArrayType(erasedComponentType); } // the erasure of a type variable is the erasure of its leftmost bound if (type instanceof TypeVariable<?>) { Type[] bounds = ((TypeVariable<?>) type).getBounds(); return getErasedType(bounds[0]); } // the erasure of every other type is the type itself return type; } private static Class<?> getErasedReferenceType(Type type) { Contracts.assertTrue(isReferenceType(type), "type is not a reference type: " + type); return (Class<?>) getErasedType(type); } public static boolean isArray(Type type) { return (type instanceof Class<?> && ((Class<?>) type).isArray()) || (type instanceof GenericArrayType); } public static Type getComponentType(Type type) { if (type instanceof Class<?>) { Class<?> klass = (Class<?>) type; return klass.isArray() ? klass.getComponentType() : null; } if (type instanceof GenericArrayType) { return ((GenericArrayType) type).getGenericComponentType(); } return null; } private static Type getArrayType(Type componentType) { Contracts.assertNotNull(componentType, "componentType"); if (componentType instanceof Class<?>) { return Array.newInstance((Class<?>) componentType, 0).getClass(); } return genericArrayType(componentType); } /** * Creates a generic array type for the specified component type. * * @param componentType the component type * @return the generic array type */ public static GenericArrayType genericArrayType(final Type componentType) { return new GenericArrayType() { @Override public Type getGenericComponentType() { return componentType; } }; } public static boolean isInstance(Type type, Object object) { return getErasedReferenceType(type).isInstance(object); } /** * Creates a parameterized type for the specified raw type and actual type arguments. * * @param rawType the raw type * @param actualTypeArguments the actual type arguments * @return the parameterized type * @throws MalformedParameterizedTypeException if the number of actual type arguments differs from * those defined on the raw type */ public static ParameterizedType parameterizedType( final Class<?> rawType, final Type... actualTypeArguments) { return new ParameterizedType() { @Override public Type[] getActualTypeArguments() { return actualTypeArguments; } @Override public Type getRawType() { return rawType; } @Override public Type getOwnerType() { return null; } }; } private static Type getResolvedSuperclass(Type type) { Contracts.assertNotNull(type, "type"); Class<?> rawType = getErasedReferenceType(type); Type supertype = rawType.getGenericSuperclass(); if (supertype == null) { return null; } return resolveTypeVariables(supertype, type); } private static Type[] getResolvedInterfaces(Type type) { Contracts.assertNotNull(type, "type"); Class<?> rawType = getErasedReferenceType(type); Type[] interfaces = rawType.getGenericInterfaces(); Type[] resolvedInterfaces = new Type[interfaces.length]; for (int i = 0; i < interfaces.length; i++) { resolvedInterfaces[i] = resolveTypeVariables(interfaces[i], type); } return resolvedInterfaces; } /** * @param annotationType The annotation type. * @param validators List of constraint validator classes (for a given constraint). * @param <A> the type of the annotation * @return Return a Map<Class, Class<? extends ConstraintValidator>> where the map key * is the type the validator accepts and value the validator class itself. */ public static <A extends Annotation> Map<Type, Class<? extends ConstraintValidator<A, ?>>> getValidatorsTypes( Class<A> annotationType, List<Class<? extends ConstraintValidator<A, ?>>> validators) { Map<Type, Class<? extends ConstraintValidator<A, ?>>> validatorsTypes = newHashMap(); for (Class<? extends ConstraintValidator<A, ?>> validator : validators) { Type type = extractType(validator); if (validatorsTypes.containsKey(type)) { throw log.getMultipleValidatorsForSameTypeException( annotationType.getName(), type.toString()); } validatorsTypes.put(type, validator); } return validatorsTypes; } private static Type extractType(Class<? extends ConstraintValidator<?, ?>> validator) { Map<Type, Type> resolvedTypes = newHashMap(); Type constraintValidatorType = resolveTypes(resolvedTypes, validator); // we now have all bind from a type to its resolution at one level Type validatorType = ((ParameterizedType) constraintValidatorType) .getActualTypeArguments()[VALIDATOR_TYPE_INDEX]; if (validatorType == null) { throw log.getNullIsAnInvalidTypeForAConstraintValidatorException(); } else if (validatorType instanceof GenericArrayType) { validatorType = TypeHelper.getArrayType(TypeHelper.getComponentType(validatorType)); } while (resolvedTypes.containsKey(validatorType)) { validatorType = resolvedTypes.get(validatorType); } // FIXME raise an exception if validatorType is not a class return validatorType; } private static Type resolveTypes(Map<Type, Type> resolvedTypes, Type type) { if (type == null) { return null; } else if (type instanceof Class) { Class<?> clazz = (Class<?>) type; final Type returnedType = resolveTypeForClassAndHierarchy(resolvedTypes, clazz); if (returnedType != null) { return returnedType; } } else if (type instanceof ParameterizedType) { ParameterizedType paramType = (ParameterizedType) type; if (!(paramType.getRawType() instanceof Class)) { return null; // don't know what to do here } Class<?> rawType = (Class<?>) paramType.getRawType(); TypeVariable<?>[] originalTypes = rawType.getTypeParameters(); Type[] partiallyResolvedTypes = paramType.getActualTypeArguments(); int nbrOfParams = originalTypes.length; for (int i = 0; i < nbrOfParams; i++) { resolvedTypes.put(originalTypes[i], partiallyResolvedTypes[i]); } if (rawType.equals(ConstraintValidator.class)) { // we found our baby return type; } else { Type returnedType = resolveTypeForClassAndHierarchy(resolvedTypes, rawType); if (returnedType != null) { return returnedType; } } } // else we don't care I think return null; } private static Type resolveTypeForClassAndHierarchy( Map<Type, Type> resolvedTypes, Class<?> clazz) { Type returnedType = resolveTypes(resolvedTypes, clazz.getGenericSuperclass()); if (returnedType != null) { return returnedType; } for (Type genericInterface : clazz.getGenericInterfaces()) { returnedType = resolveTypes(resolvedTypes, genericInterface); if (returnedType != null) { return returnedType; } } return null; } private static void putPrimitiveSubtypes( Map<Class<?>, Set<Class<?>>> subtypesByPrimitive, Class<?> primitiveType, Class<?>... directSubtypes) { Set<Class<?>> subtypes = newHashSet(); for (Class<?> directSubtype : directSubtypes) { subtypes.add(directSubtype); subtypes.addAll(subtypesByPrimitive.get(directSubtype)); } subtypesByPrimitive.put(primitiveType, Collections.unmodifiableSet(subtypes)); } private static boolean isClassAssignable(Class<?> supertype, Class<?> type) { // Class.isAssignableFrom does not perform primitive widening if (supertype.isPrimitive() && type.isPrimitive()) { return SUBTYPES_BY_PRIMITIVE.get(supertype).contains(type); } return supertype.isAssignableFrom(type); } private static boolean isClassAssignableToWildcardType(Class<?> supertype, WildcardType type) { for (Type upperBound : type.getUpperBounds()) { if (!isAssignable(supertype, upperBound)) { return false; } } return true; } private static boolean isParameterizedTypeAssignable( ParameterizedType supertype, ParameterizedType type) { Type rawSupertype = supertype.getRawType(); Type rawType = type.getRawType(); if (!rawSupertype.equals(rawType)) { // short circuit when class raw types are unassignable if (rawSupertype instanceof Class<?> && rawType instanceof Class<?> && !(((Class<?>) rawSupertype).isAssignableFrom((Class<?>) rawType))) { return false; } return isSuperAssignable(supertype, type); } Type[] supertypeArgs = supertype.getActualTypeArguments(); Type[] typeArgs = type.getActualTypeArguments(); if (supertypeArgs.length != typeArgs.length) { return false; } for (int i = 0; i < supertypeArgs.length; i++) { Type supertypeArg = supertypeArgs[i]; Type typeArg = typeArgs[i]; if (supertypeArg instanceof WildcardType) { if (!isWildcardTypeAssignable((WildcardType) supertypeArg, typeArg)) { return false; } } else if (!supertypeArg.equals(typeArg)) { return false; } } return true; } private static boolean isTypeVariableAssignable(Type supertype, TypeVariable<?> type) { for (Type bound : type.getBounds()) { if (isAssignable(supertype, bound)) { return true; } } return false; } private static boolean isWildcardTypeAssignable(WildcardType supertype, Type type) { for (Type upperBound : supertype.getUpperBounds()) { if (!isAssignable(upperBound, type)) { return false; } } for (Type lowerBound : supertype.getLowerBounds()) { if (!isAssignable(type, lowerBound)) { return false; } } return true; } private static boolean isSuperAssignable(Type supertype, Type type) { Type superclass = getResolvedSuperclass(type); if (superclass != null && isAssignable(supertype, superclass)) { return true; } for (Type interphace : getResolvedInterfaces(type)) { if (isAssignable(supertype, interphace)) { return true; } } return false; } /** * Gets whether the specified type is a <em>reference type</em>. * * <p>More specifically, this method returns {@code true} if the specified type is one of the * following: * * <ul> * <li>a class type * <li>an interface type * <li>an array type * <li>a parameterized type * <li>a type variable * <li>the null type * </ul> * * @param type the type to check * @return {@code true} if the specified type is a reference type * @see <a href="http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.3">4.3 * Reference Types and Values</a> */ private static boolean isReferenceType(Type type) { return type == null || type instanceof Class<?> || type instanceof ParameterizedType || type instanceof TypeVariable<?> || type instanceof GenericArrayType; } private static boolean isArraySupertype(Class<?> type) { return Object.class.equals(type) || Cloneable.class.equals(type) || Serializable.class.equals(type); } private static Type resolveTypeVariables(Type type, Type subtype) { // TODO: need to support other types in future, e.g. T[], etc. if (!(type instanceof ParameterizedType)) { return type; } Map<Type, Type> actualTypeArgumentsByParameter = getActualTypeArgumentsByParameter(type, subtype); Class<?> rawType = getErasedReferenceType(type); return parameterizeClass(rawType, actualTypeArgumentsByParameter); } private static Map<Type, Type> getActualTypeArgumentsByParameter(Type... types) { // TODO: return Map<TypeVariable<Class<?>>, Type> somehow Map<Type, Type> actualTypeArgumentsByParameter = new LinkedHashMap<Type, Type>(); for (Type type : types) { actualTypeArgumentsByParameter.putAll(getActualTypeArgumentsByParameterInternal(type)); } return normalize(actualTypeArgumentsByParameter); } private static Map<Type, Type> getActualTypeArgumentsByParameterInternal(Type type) { // TODO: look deeply within non-parameterized types when visitors implemented if (!(type instanceof ParameterizedType)) { return Collections.emptyMap(); } TypeVariable<?>[] typeParameters = getErasedReferenceType(type).getTypeParameters(); Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments(); if (typeParameters.length != typeArguments.length) { throw new MalformedParameterizedTypeException(); } Map<Type, Type> actualTypeArgumentsByParameter = new LinkedHashMap<Type, Type>(); for (int i = 0; i < typeParameters.length; i++) { actualTypeArgumentsByParameter.put(typeParameters[i], typeArguments[i]); } return actualTypeArgumentsByParameter; } private static ParameterizedType parameterizeClass( Class<?> type, Map<Type, Type> actualTypeArgumentsByParameter) { return parameterizeClassCapture(type, actualTypeArgumentsByParameter); } private static <T> ParameterizedType parameterizeClassCapture( Class<T> type, Map<Type, Type> actualTypeArgumentsByParameter) { // TODO: actualTypeArgumentsByParameter should be Map<TypeVariable<Class<T>>, Type> TypeVariable<Class<T>>[] typeParameters = type.getTypeParameters(); Type[] actualTypeArguments = new Type[typeParameters.length]; for (int i = 0; i < typeParameters.length; i++) { TypeVariable<Class<T>> typeParameter = typeParameters[i]; Type actualTypeArgument = actualTypeArgumentsByParameter.get(typeParameter); if (actualTypeArgument == null) { throw log.getMissingActualTypeArgumentForTypeParameterException(typeParameter); } actualTypeArguments[i] = actualTypeArgument; } return parameterizedType(getErasedReferenceType(type), actualTypeArguments); } private static <K, V> Map<K, V> normalize(Map<K, V> map) { // TODO: will this cause an infinite loop with recursive bounds? for (Entry<K, V> entry : map.entrySet()) { K key = entry.getKey(); V value = entry.getValue(); while (map.containsKey(value)) { value = map.get(value); } map.put(key, value); } return map; } }