org.hibernate.validation.metadata.BeanMetaDataImpl Maven / Gradle / Ivy
// $Id: BeanMetaDataImpl.java 17263 2009-08-11 18:00:25Z epbernard $
/*
* JBoss, Home of Professional Open Source
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.hibernate.validation.metadata;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.security.AccessController;
import javax.validation.GroupDefinitionException;
import javax.validation.GroupSequence;
import javax.validation.Valid;
import javax.validation.ValidationException;
import javax.validation.groups.Default;
import javax.validation.metadata.BeanDescriptor;
import javax.validation.metadata.PropertyDescriptor;
import org.slf4j.Logger;
import org.hibernate.validation.util.LoggerFactory;
import org.hibernate.validation.util.ReflectionHelper;
import org.hibernate.validation.util.GetDeclaredFields;
import org.hibernate.validation.util.GetDeclaredMethods;
import org.hibernate.validation.util.SetAccessibility;
/**
* 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
*/
public class BeanMetaDataImpl implements BeanMetaData {
private static final Logger log = LoggerFactory.make();
/**
* The root bean class for this validator.
*/
private final Class beanClass;
/**
* The main element descriptor for beanClass
.
*/
private BeanDescriptorImpl beanDescriptor;
/**
* Map of all direct constraints which belong to the entity {@code beanClass}. The constraints are mapped to the class
* (eg super class or interface) in which they are defined.
*/
private Map, List>> metaConstraints = new HashMap, List>>();
/**
* List of cascaded members.
*/
private List cascadedMembers = new ArrayList();
/**
* Maps field and method names to their ElementDescriptorImpl
.
*/
private Map propertyDescriptors = new HashMap();
/**
* Maps group sequences to the list of group/sequences.
*/
private List> defaultGroupSequence = new ArrayList>();
/**
* Object keeping track of all constraints.
*/
private final ConstraintHelper constraintHelper;
//updated on the fly, needs to be thread safe
//property name
private final Set propertyNames = new HashSet(30);
public BeanMetaDataImpl(Class beanClass, ConstraintHelper constraintHelper) {
this(
beanClass,
constraintHelper,
new AnnotationIgnores()
);
}
public BeanMetaDataImpl(Class beanClass, ConstraintHelper constraintHelper, AnnotationIgnores annotationIgnores) {
this.beanClass = beanClass;
this.constraintHelper = constraintHelper;
createMetaData( annotationIgnores );
}
public Class getBeanClass() {
return beanClass;
}
public BeanDescriptor getBeanDescriptor() {
return beanDescriptor;
}
public List getCascadedMembers() {
return Collections.unmodifiableList( cascadedMembers );
}
public Map, List>> geMetaConstraintsAsMap() {
return Collections.unmodifiableMap( metaConstraints );
}
public List> geMetaConstraintsAsList() {
List> constraintList = new ArrayList>();
for ( List> list : metaConstraints.values() ) {
constraintList.addAll( list );
}
return Collections.unmodifiableList( constraintList );
}
public void addMetaConstraint(Class> clazz, MetaConstraint metaConstraint) {
List> constraintList;
if ( !metaConstraints.containsKey( clazz ) ) {
constraintList = new ArrayList>();
metaConstraints.put( clazz, constraintList );
}
else {
constraintList = metaConstraints.get( clazz );
}
constraintList.add( metaConstraint );
}
public void addCascadedMember(Member member) {
cascadedMembers.add( member );
}
public PropertyDescriptor getPropertyDescriptor(String property) {
return propertyDescriptors.get( property );
}
public boolean isPropertyPresent(String name) {
return propertyNames.contains( name ); //To change body of implemented methods use File | Settings | File Templates.
}
public List> getDefaultGroupSequence() {
return Collections.unmodifiableList( defaultGroupSequence );
}
public boolean defaultGroupSequenceIsRedefined() {
return defaultGroupSequence.size() > 1;
}
public void setDefaultGroupSequence(List> groupSequence) {
defaultGroupSequence = new ArrayList>();
boolean groupSequenceContainsDefault = false;
for ( Class> group : groupSequence ) {
if ( group.getName().equals( beanClass.getName() ) ) {
defaultGroupSequence.add( Default.class );
groupSequenceContainsDefault = true;
}
else if ( group.getName().equals( Default.class.getName() ) ) {
throw new GroupDefinitionException( "'Default.class' cannot appear in default group sequence list." );
}
else {
defaultGroupSequence.add( group );
}
}
if ( !groupSequenceContainsDefault ) {
throw new GroupDefinitionException( beanClass.getName() + " must be part of the redefined default group sequence." );
}
if ( log.isTraceEnabled() ) {
log.trace(
"Members of the default group sequence for bean {} are: {}",
beanClass.getName(),
defaultGroupSequence
);
}
}
public Set getConstrainedProperties() {
return Collections.unmodifiableSet( new HashSet( propertyDescriptors.values() ) );
}
/**
* Create bean desciptor, find all classes/subclasses/interfaces which have to be taken in consideration
* for this validator and create meta data.
*
* @param annotationIgnores Datastructure keeping track on which annotation should be ignored.
*/
private void createMetaData(AnnotationIgnores annotationIgnores) {
beanDescriptor = new BeanDescriptorImpl( this );
initDefaultGroupSequence();
List classes = new ArrayList();
computeClassHierarchy( beanClass, classes );
for ( Class current : classes ) {
initClass( current, annotationIgnores );
}
}
/**
* Get all superclasses and interfaces recursively.
*
* @param clazz The class to start the search with.
* @param classes List of classes to which to add all found super classes and interfaces.
*/
private void computeClassHierarchy(Class clazz, List classes) {
if ( log.isTraceEnabled() ) {
log.trace( "Processing: {}", clazz );
}
for ( Class current = clazz; current != null; current = current.getSuperclass() ) {
if ( classes.contains( current ) ) {
return;
}
classes.add( current );
for ( Class currentInterface : current.getInterfaces() ) {
computeClassHierarchy( currentInterface, classes );
}
}
}
private void initClass(Class clazz, AnnotationIgnores annotationIgnores) {
initClassConstraints( clazz, annotationIgnores );
initMethodConstraints( clazz, annotationIgnores );
initFieldConstraints( clazz, annotationIgnores );
}
/**
* Checks whether there is a default group sequence defined for this class.
* See HV-113.
*/
private void initDefaultGroupSequence() {
List> groupSequence = new ArrayList>();
GroupSequence groupSequenceAnnotation = beanClass.getAnnotation( GroupSequence.class );
if ( groupSequenceAnnotation == null ) {
groupSequence.add( beanClass );
}
else {
groupSequence.addAll( Arrays.asList( groupSequenceAnnotation.value() ) );
}
setDefaultGroupSequence( groupSequence );
}
private void initFieldConstraints(Class> clazz, AnnotationIgnores annotationIgnores) {
GetDeclaredFields action = GetDeclaredFields.action( clazz );
final Field[] fields;
if ( System.getSecurityManager() != null ) {
fields = AccessController.doPrivileged( action );
}
else {
fields = action.run();
}
for ( Field field : fields ) {
// HV-172
if ( Modifier.isStatic( field.getModifiers() ) ) {
continue;
}
String name = ReflectionHelper.getPropertyName( field );
if (name != null) {
propertyNames.add( name );
}
List> fieldMetadata = findConstraints( field );
for ( ConstraintDescriptorImpl> constraintDescription : fieldMetadata ) {
if ( annotationIgnores.isIgnoreAnnotations( field ) ) {
break;
}
setAccessibility( field );
MetaConstraint metaConstraint = createMetaConstraint( field, constraintDescription );
addMetaConstraint( clazz, metaConstraint );
}
if ( field.isAnnotationPresent( Valid.class ) ) {
setAccessibility( field );
cascadedMembers.add( field );
addPropertyDescriptorForMember( field );
}
}
}
private void setAccessibility(Member member) {
SetAccessibility action = SetAccessibility.action( member );
if (System.getSecurityManager() != null) {
AccessController.doPrivileged( action );
}
else {
action.run();
}
}
private void initMethodConstraints(Class> clazz, AnnotationIgnores annotationIgnores) {
GetDeclaredMethods action = GetDeclaredMethods.action( clazz );
final Method[] declaredMethods;
if ( System.getSecurityManager() != null ) {
declaredMethods = AccessController.doPrivileged( action );
}
else {
declaredMethods = action.run();
}
for ( Method method : declaredMethods ) {
// HV-172
if ( Modifier.isStatic( method.getModifiers() ) ) {
continue;
}
String name = ReflectionHelper.getPropertyName( method );
if (name != null) {
propertyNames.add( name );
}
List> methodMetadata = findConstraints( method );
for ( ConstraintDescriptorImpl> constraintDescription : methodMetadata ) {
if ( annotationIgnores.isIgnoreAnnotations( method ) ) {
break;
}
setAccessibility( method );
MetaConstraint metaConstraint = createMetaConstraint( method, constraintDescription );
addMetaConstraint( clazz, metaConstraint );
}
if ( method.isAnnotationPresent( Valid.class ) ) {
setAccessibility( method );
cascadedMembers.add( method );
addPropertyDescriptorForMember( method );
}
}
}
private PropertyDescriptorImpl addPropertyDescriptorForMember(Member member) {
String name = ReflectionHelper.getPropertyName( member );
PropertyDescriptorImpl propertyDescriptor = ( PropertyDescriptorImpl ) propertyDescriptors.get(
name
);
if ( propertyDescriptor == null ) {
propertyDescriptor = new PropertyDescriptorImpl(
ReflectionHelper.getType( member ),
( ( AnnotatedElement ) member ).isAnnotationPresent( Valid.class ),
name
);
propertyDescriptors.put( name, propertyDescriptor );
}
return propertyDescriptor;
}
private void initClassConstraints(Class> clazz, AnnotationIgnores annotationIgnores) {
if ( annotationIgnores.isIgnoreAnnotations( clazz ) ) {
return;
}
List> classMetadata = findClassLevelConstraints( clazz );
for ( ConstraintDescriptorImpl> constraintDescription : classMetadata ) {
MetaConstraint metaConstraint = createMetaConstraint( constraintDescription );
addMetaConstraint( clazz, metaConstraint );
}
}
private MetaConstraint createMetaConstraint(ConstraintDescriptorImpl descriptor) {
return new MetaConstraint( beanClass, descriptor );
}
private MetaConstraint createMetaConstraint(Member m, ConstraintDescriptorImpl descriptor) {
return new MetaConstraint( m, beanClass, descriptor );
}
/**
* Examines the given annotation to see whether it is a single or multi valued constraint annotation.
*
* @param clazz the class we are currently processing.
* @param annotation The annotation to examine.
*
* @return A list of constraint descriptors or the empty list in case annotation
is neither a
* single nor multi value annotation.
*/
private List> findConstraintAnnotations(Class> clazz, A annotation) {
List> constraintDescriptors = new ArrayList>();
List constraints = new ArrayList();
if ( constraintHelper.isConstraintAnnotation( annotation ) ||
constraintHelper.isBuiltinConstraint( annotation.annotationType() ) ) {
constraints.add( annotation );
}
// check if we have a multi value constraint
constraints.addAll( constraintHelper.getMultiValueConstraints( annotation ) );
for ( Annotation constraint : constraints ) {
final ConstraintDescriptorImpl constraintDescriptor = buildConstraintDescriptor( clazz, constraint );
constraintDescriptors.add( constraintDescriptor );
}
return constraintDescriptors;
}
@SuppressWarnings("unchecked")
private ConstraintDescriptorImpl buildConstraintDescriptor(Class> clazz, A annotation) {
ConstraintDescriptorImpl constraintDescriptor;
if ( clazz.isInterface() && !clazz.equals( beanClass ) ) {
constraintDescriptor = new ConstraintDescriptorImpl( annotation, constraintHelper, clazz );
}
else {
constraintDescriptor = new ConstraintDescriptorImpl( annotation, constraintHelper );
}
return constraintDescriptor;
}
/**
* Finds all constraint annotations defined for the given class and returns them in a list of
* constraint descriptors.
*
* @param beanClass The class to check for constraints annotations.
*
* @return A list of constraint descriptors for all constraint specified on the given class.
*/
private List> findClassLevelConstraints(Class> beanClass) {
List> metadata = new ArrayList>();
for ( Annotation annotation : beanClass.getAnnotations() ) {
metadata.addAll( findConstraintAnnotations( beanClass, annotation ) );
}
for ( ConstraintDescriptorImpl constraintDescriptor : metadata ) {
beanDescriptor.addConstraintDescriptor( constraintDescriptor );
}
return metadata;
}
/**
* Finds all constraint annotations defined for the given field/method and returns them in a list of
* constraint descriptors.
*
* @param member The fiels or method to check for constraints annotations.
*
* @return A list of constraint descriptors for all constraint specified for the given field or method.
*/
private List> findConstraints(Member member) {
assert member instanceof Field || member instanceof Method;
List> metadata = new ArrayList>();
for ( Annotation annotation : ( ( AnnotatedElement ) member ).getAnnotations() ) {
metadata.addAll( findConstraintAnnotations( member.getDeclaringClass(), annotation ) );
}
String name = ReflectionHelper.getPropertyName( member );
for ( ConstraintDescriptorImpl constraintDescriptor : metadata ) {
if ( member instanceof Method && name == null ) { // can happen if member is a Method which does not follow the bean convention
throw new ValidationException(
"Annotated methods must follow the JavaBeans naming convention. " + member.getName() + "() does not."
);
}
PropertyDescriptorImpl propertyDescriptor = ( PropertyDescriptorImpl ) propertyDescriptors.get( name );
if ( propertyDescriptor == null ) {
propertyDescriptor = addPropertyDescriptorForMember( member );
}
propertyDescriptor.addConstraintDescriptor( constraintDescriptor );
}
return metadata;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append( "BeanMetaDataImpl" );
sb.append( "{beanClass=" ).append( beanClass );
sb.append( ", beanDescriptor=" ).append( beanDescriptor );
sb.append( ", metaConstraints=" ).append( metaConstraints );
sb.append( ", cascadedMembers=" ).append( cascadedMembers );
sb.append( ", propertyDescriptors=" ).append( propertyDescriptors );
sb.append( ", defaultGroupSequence=" ).append( defaultGroupSequence );
sb.append( ", constraintHelper=" ).append( constraintHelper );
sb.append( '}' );
return sb.toString();
}
}