HHH-10050 - AttributeConverter should supports ParameterizedType if autoApply is true

This commit is contained in:
Steve Ebersole 2015-10-06 16:07:33 -05:00
parent 9633c0a6e5
commit 0cf66b85e0
22 changed files with 1002 additions and 171 deletions

View File

@ -395,7 +395,6 @@ public interface MetadataBuilder {
MetadataBuilder applyAuxiliaryDatabaseObject(AuxiliaryDatabaseObject auxiliaryDatabaseObject); MetadataBuilder applyAuxiliaryDatabaseObject(AuxiliaryDatabaseObject auxiliaryDatabaseObject);
/** /**
* Adds an AttributeConverter by a AttributeConverterDefinition * Adds an AttributeConverter by a AttributeConverterDefinition
* *

View File

@ -0,0 +1,248 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.boot.internal;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.persistence.AttributeConverter;
import org.hibernate.AnnotationException;
import org.hibernate.HibernateException;
import org.hibernate.annotations.common.reflection.ReflectionManager;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.annotations.common.reflection.java.JavaXMember;
import org.hibernate.boot.spi.AttributeConverterDescriptor;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.cfg.AttributeConverterDefinition;
import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.ResolvedTypeWithMembers;
import com.fasterxml.classmate.members.ResolvedField;
import com.fasterxml.classmate.members.ResolvedMember;
import com.fasterxml.classmate.members.ResolvedMethod;
/**
* The standard AttributeConverterDescriptor implementation
*
* @author Steve Ebersole
*/
public class AttributeConverterDescriptorImpl implements AttributeConverterDescriptor {
private final AttributeConverter attributeConverter;
private final boolean autoApply;
private final ResolvedType domainType;
private final ResolvedType jdbcType;
public static AttributeConverterDescriptor create(
AttributeConverterDefinition definition,
ClassmateContext classmateContext) {
final AttributeConverter converter = definition.getAttributeConverter();
final Class converterClass = converter.getClass();
final ResolvedType converterType = classmateContext.getTypeResolver().resolve( converterClass );
final List<ResolvedType> converterParamTypes = converterType.typeParametersFor( AttributeConverter.class );
if ( converterParamTypes == null ) {
throw new AnnotationException(
"Could not extract type parameter information from AttributeConverter implementation ["
+ converterClass.getName() + "]"
);
}
else if ( converterParamTypes.size() != 2 ) {
throw new AnnotationException(
"Unexpected type parameter information for AttributeConverter implementation [" +
converterClass.getName() + "]; expected 2 parameter types, but found " + converterParamTypes.size()
);
}
return new AttributeConverterDescriptorImpl(
converter,
definition.isAutoApply(),
converterParamTypes.get( 0 ),
converterParamTypes.get( 1 )
);
}
private AttributeConverterDescriptorImpl(
AttributeConverter attributeConverter,
boolean autoApply,
ResolvedType domainType,
ResolvedType jdbcType) {
this.attributeConverter = attributeConverter;
this.autoApply = autoApply;
this.domainType = domainType;
this.jdbcType = jdbcType;
}
@Override
public AttributeConverter getAttributeConverter() {
return attributeConverter;
}
@Override
public Class<?> getDomainType() {
return domainType.getErasedType();
}
@Override
public Class<?> getJdbcType() {
return jdbcType.getErasedType();
}
@Override
@SuppressWarnings("SimplifiableIfStatement")
public boolean shouldAutoApplyToAttribute(XProperty xProperty, MetadataBuildingContext context) {
if ( !autoApply ) {
return false;
}
final ResolvedType attributeType = resolveAttributeType( xProperty, context );
return typesMatch( domainType, attributeType );
}
private ResolvedType resolveAttributeType(XProperty xProperty, MetadataBuildingContext context) {
return resolveMember( xProperty, context ).getType();
}
private ResolvedMember resolveMember(XProperty xProperty, MetadataBuildingContext buildingContext) {
final ClassmateContext classmateContext = buildingContext.getMetadataCollector().getClassmateContext();
final ReflectionManager reflectionManager = buildingContext.getBuildingOptions().getReflectionManager();
final ResolvedType declaringClassType = classmateContext.getTypeResolver().resolve(
reflectionManager.toClass( xProperty.getDeclaringClass() )
);
final ResolvedTypeWithMembers declaringClassWithMembers = classmateContext.getMemberResolver().resolve(
declaringClassType,
null,
null
);
final Member member = toMember( xProperty );
if ( member instanceof Method ) {
for ( ResolvedMethod resolvedMember : declaringClassWithMembers.getMemberMethods() ) {
if ( resolvedMember.getName().equals( member.getName() ) ) {
return resolvedMember;
}
}
}
else if ( member instanceof Field ) {
for ( ResolvedField resolvedMember : declaringClassWithMembers.getMemberFields() ) {
if ( resolvedMember.getName().equals( member.getName() ) ) {
return resolvedMember;
}
}
}
else {
throw new HibernateException( "Unexpected java.lang.reflect.Member type from org.hibernate.annotations.common.reflection.java.JavaXMember : " + member );
}
throw new HibernateException(
"Could not locate resolved type information for attribute [" + member.getName() + "] from Classmate"
);
}
private static Method memberMethod;
private static Member toMember(XProperty xProperty) {
if ( memberMethod == null ) {
Class<JavaXMember> javaXMemberClass = JavaXMember.class;
try {
memberMethod = javaXMemberClass.getDeclaredMethod( "getMember" );
memberMethod.setAccessible( true );
}
catch (NoSuchMethodException e) {
throw new HibernateException( "Could not access org.hibernate.annotations.common.reflection.java.JavaXMember#getMember method", e );
}
}
try {
return (Member) memberMethod.invoke( xProperty );
}
catch (Exception e) {
throw new HibernateException( "Could not access org.hibernate.annotations.common.reflection.java.JavaXMember#getMember method", e );
}
}
private boolean typesMatch(ResolvedType converterDefinedType, ResolvedType checkType) {
if ( !checkType.getErasedType().isAssignableFrom( converterDefinedType.getErasedType() ) ) {
return false;
}
// if the converter did not define any nested type parameters, then the check above is
// enough for a match
if ( converterDefinedType.getTypeParameters().isEmpty() ) {
return true;
}
// however, here the converter *did* define nested type parameters, so we'd have a converter defined using something like, e.g., List<String> for its
// domain type.
//
// we need to check those nested types as well
if ( checkType.getTypeParameters().isEmpty() ) {
// the domain type did not define nested type params. a List<String> would not auto-match a List(<Object>)
return false;
}
if ( converterDefinedType.getTypeParameters().size() != checkType.getTypeParameters().size() ) {
// they had different number of type params somehow.
return false;
}
for ( int i = 0; i < converterDefinedType.getTypeParameters().size(); i++ ) {
if ( !typesMatch( converterDefinedType.getTypeParameters().get( i ), checkType.getTypeParameters().get( i ) ) ) {
return false;
}
}
return true;
}
@Override
public boolean shouldAutoApplyToCollectionElement(XProperty xProperty, MetadataBuildingContext context) {
if ( !autoApply ) {
return false;
}
final ResolvedMember collectionMember = resolveMember( xProperty, context );
final ResolvedType elementType;
if ( Map.class.isAssignableFrom( collectionMember.getType().getErasedType() ) ) {
elementType = collectionMember.getType().typeParametersFor( Map.class ).get( 1 );
}
else if ( Collection.class.isAssignableFrom( collectionMember.getType().getErasedType() ) ) {
elementType = collectionMember.getType().typeParametersFor( Collection.class ).get( 0 );
}
else {
throw new HibernateException( "Attribute was neither a Collection nor a Map : " + collectionMember.getType().getErasedType() );
}
return typesMatch( domainType, elementType );
}
@Override
public boolean shouldAutoApplyToMapKey(XProperty xProperty, MetadataBuildingContext context) {
if ( !autoApply ) {
return false;
}
final ResolvedMember collectionMember = resolveMember( xProperty, context );
final ResolvedType keyType;
if ( Map.class.isAssignableFrom( collectionMember.getType().getErasedType() ) ) {
keyType = collectionMember.getType().typeParametersFor( Map.class ).get( 0 );
}
else {
throw new HibernateException( "Attribute was not a Map : " + collectionMember.getType().getErasedType() );
}
return typesMatch( domainType, keyType );
}
}

View File

@ -0,0 +1,199 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.boot.internal;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.persistence.AttributeConverter;
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.spi.AttributeConverterDescriptor;
import org.hibernate.boot.spi.MetadataBuildingContext;
/**
* Special-use AttributeConverterDescriptor implementation for cases where the converter will never
* be used for auto-apply.
*
* @author Steve Ebersole
*/
public class AttributeConverterDescriptorNonAutoApplicableImpl implements AttributeConverterDescriptor {
private final AttributeConverter converter;
private Class domainType;
private Class jdbcType;
public AttributeConverterDescriptorNonAutoApplicableImpl(AttributeConverter converter) {
this.converter = converter;
final Class attributeConverterClass = converter.getClass();
final ParameterizedType attributeConverterSignature = extractAttributeConverterParameterizedType(
attributeConverterClass
);
if ( attributeConverterSignature == null ) {
throw new AssertionFailure(
"Could not extract ParameterizedType representation of AttributeConverter definition " +
"from AttributeConverter implementation class [" + attributeConverterClass.getName() + "]"
);
}
if ( attributeConverterSignature.getActualTypeArguments().length < 2 ) {
throw new AnnotationException(
"AttributeConverter [" + attributeConverterClass.getName()
+ "] did not retain parameterized type information"
);
}
if ( attributeConverterSignature.getActualTypeArguments().length > 2 ) {
throw new AnnotationException(
"AttributeConverter [" + attributeConverterClass.getName()
+ "] specified more than 2 parameterized types"
);
}
this.domainType = extractClass( attributeConverterSignature.getActualTypeArguments()[0] );
if ( this.domainType == null ) {
throw new AnnotationException(
"Could not determine domain type from given AttributeConverter [" +
attributeConverterClass.getName() + "]"
);
}
this.jdbcType = extractClass(attributeConverterSignature.getActualTypeArguments()[1]);
if ( this.jdbcType == null ) {
throw new AnnotationException(
"Could not determine JDBC type from given AttributeConverter [" +
attributeConverterClass.getName() + "]"
);
}
}
private ParameterizedType extractAttributeConverterParameterizedType(Type base) {
if ( base != null ) {
Class clazz = extractClass( base );
List<Type> types = new ArrayList<Type>();
types.add( clazz.getGenericSuperclass() );
types.addAll( Arrays.asList( clazz.getGenericInterfaces() ) );
for ( Type type : types ) {
type = resolveType( type, base );
if ( ParameterizedType.class.isInstance( type ) ) {
final ParameterizedType parameterizedType = (ParameterizedType) type;
if ( AttributeConverter.class.equals( parameterizedType.getRawType() ) ) {
return parameterizedType;
}
}
ParameterizedType parameterizedType = extractAttributeConverterParameterizedType( type );
if ( parameterizedType != null ) {
return parameterizedType;
}
}
}
return null;
}
private static Class extractClass(Type type) {
if ( type instanceof Class ) {
return (Class) type;
}
else if ( type instanceof ParameterizedType ) {
return extractClass( ( (ParameterizedType) type ).getRawType() );
}
return null;
}
private static Type resolveType(Type target, Type context) {
if ( target instanceof ParameterizedType ) {
return resolveParameterizedType( (ParameterizedType) target, context );
}
else if ( target instanceof TypeVariable ) {
return resolveTypeVariable( (TypeVariable) target, (ParameterizedType) context );
}
return target;
}
private static ParameterizedType resolveParameterizedType(final ParameterizedType parameterizedType, Type context) {
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
final Type[] resolvedTypeArguments = new Type[actualTypeArguments.length];
for ( int idx = 0; idx < actualTypeArguments.length; idx++ ) {
resolvedTypeArguments[idx] = resolveType( actualTypeArguments[idx], context );
}
return new ParameterizedType() {
@Override
public Type[] getActualTypeArguments() {
return resolvedTypeArguments;
}
@Override
public Type getRawType() {
return parameterizedType.getRawType();
}
@Override
public Type getOwnerType() {
return parameterizedType.getOwnerType();
}
};
}
private static Type resolveTypeVariable(TypeVariable typeVariable, ParameterizedType context) {
Class clazz = extractClass( context.getRawType() );
TypeVariable[] typeParameters = clazz.getTypeParameters();
for ( int idx = 0; idx < typeParameters.length; idx++ ) {
if ( typeVariable.getName().equals( typeParameters[idx].getName() ) ) {
return resolveType( context.getActualTypeArguments()[idx], context );
}
}
return typeVariable;
}
private static Class extractType(TypeVariable typeVariable) {
java.lang.reflect.Type[] boundTypes = typeVariable.getBounds();
if ( boundTypes == null || boundTypes.length != 1 ) {
return null;
}
return (Class) boundTypes[0];
}
@Override
public AttributeConverter getAttributeConverter() {
return converter;
}
@Override
public Class<?> getDomainType() {
return domainType;
}
@Override
public Class<?> getJdbcType() {
return jdbcType;
}
@Override
public boolean shouldAutoApplyToAttribute(XProperty xProperty, MetadataBuildingContext context) {
return false;
}
@Override
public boolean shouldAutoApplyToCollectionElement(XProperty xProperty, MetadataBuildingContext context) {
return false;
}
@Override
public boolean shouldAutoApplyToMapKey(XProperty xProperty, MetadataBuildingContext context) {
return false;
}
}

View File

@ -0,0 +1,190 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.boot.internal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.hibernate.AssertionFailure;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.spi.AttributeConverterAutoApplyHandler;
import org.hibernate.boot.spi.AttributeConverterDescriptor;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.internal.util.StringHelper;
import org.jboss.logging.Logger;
/**
* @author Steve Ebersole
*/
public class AttributeConverterManager implements AttributeConverterAutoApplyHandler {
private static final Logger log = Logger.getLogger( AttributeConverterManager.class );
private Map<Class, AttributeConverterDescriptor> attributeConverterDescriptorsByClass;
void addConverter(AttributeConverterDescriptor descriptor) {
if ( attributeConverterDescriptorsByClass == null ) {
attributeConverterDescriptorsByClass = new ConcurrentHashMap<Class, AttributeConverterDescriptor>();
}
final Object old = attributeConverterDescriptorsByClass.put(
descriptor.getAttributeConverter().getClass(),
descriptor
);
if ( old != null ) {
throw new AssertionFailure(
String.format(
Locale.ENGLISH,
"AttributeConverter class [%s] registered multiple times",
descriptor.getAttributeConverter().getClass()
)
);
}
}
private Collection<AttributeConverterDescriptor> converterDescriptors() {
if ( attributeConverterDescriptorsByClass == null ) {
return Collections.emptyList();
}
return attributeConverterDescriptorsByClass.values();
}
@Override
public AttributeConverterDescriptor findAutoApplyConverterForAttribute(
XProperty xProperty,
MetadataBuildingContext context) {
List<AttributeConverterDescriptor> matched = new ArrayList<AttributeConverterDescriptor>();
for ( AttributeConverterDescriptor descriptor : converterDescriptors() ) {
log.debugf(
"Checking auto-apply AttributeConverter [%s] (type=%s) for match against attribute : %s.%s (type=%s)",
descriptor.toString(),
descriptor.getDomainType().getSimpleName(),
xProperty.getDeclaringClass().getName(),
xProperty.getName(),
xProperty.getType().getName()
);
if ( descriptor.shouldAutoApplyToAttribute( xProperty, context ) ) {
matched.add( descriptor );
}
}
if ( matched.isEmpty() ) {
return null;
}
if ( matched.size() == 1 ) {
return matched.get( 0 );
}
// otherwise, we had multiple matches
throw new RuntimeException(
String.format(
Locale.ROOT,
"Multiple auto-apply converters matched attribute [%s.%s] : %s",
xProperty.getDeclaringClass().getName(),
xProperty.getName(),
StringHelper.join( matched, RENDERER )
)
);
}
@Override
public AttributeConverterDescriptor findAutoApplyConverterForCollectionElement(
XProperty xProperty,
MetadataBuildingContext context) {
List<AttributeConverterDescriptor> matched = new ArrayList<AttributeConverterDescriptor>();
for ( AttributeConverterDescriptor descriptor : converterDescriptors() ) {
log.debugf(
"Checking auto-apply AttributeConverter [%s] (type=%s) for match against collection attribute's element : %s.%s (type=%s)",
descriptor.toString(),
descriptor.getDomainType().getSimpleName(),
xProperty.getDeclaringClass().getName(),
xProperty.getName(),
xProperty.getElementClass().getName()
);
if ( descriptor.shouldAutoApplyToCollectionElement( xProperty, context ) ) {
matched.add( descriptor );
}
}
if ( matched.isEmpty() ) {
return null;
}
if ( matched.size() == 1 ) {
return matched.get( 0 );
}
// otherwise, we had multiple matches
throw new RuntimeException(
String.format(
Locale.ROOT,
"Multiple auto-apply converters matched attribute [%s.%s] : %s",
xProperty.getDeclaringClass().getName(),
xProperty.getName(),
StringHelper.join( matched, RENDERER )
)
);
}
@Override
public AttributeConverterDescriptor findAutoApplyConverterForMapKey(
XProperty xProperty,
MetadataBuildingContext context) {
List<AttributeConverterDescriptor> matched = new ArrayList<AttributeConverterDescriptor>();
for ( AttributeConverterDescriptor descriptor : converterDescriptors() ) {
log.debugf(
"Checking auto-apply AttributeConverter [%s] (type=%s) for match against map attribute's key : %s.%s (type=%s)",
descriptor.toString(),
descriptor.getDomainType().getSimpleName(),
xProperty.getDeclaringClass().getName(),
xProperty.getName(),
xProperty.getMapKey().getName()
);
if ( descriptor.shouldAutoApplyToMapKey( xProperty, context ) ) {
matched.add( descriptor );
}
}
if ( matched.isEmpty() ) {
return null;
}
if ( matched.size() == 1 ) {
return matched.get( 0 );
}
// otherwise, we had multiple matches
throw new RuntimeException(
String.format(
Locale.ROOT,
"Multiple auto-apply converters matched attribute [%s.%s] : %s",
xProperty.getDeclaringClass().getName(),
xProperty.getName(),
StringHelper.join( matched, RENDERER )
)
);
}
private static StringHelper.Renderer<AttributeConverterDescriptor> RENDERER = new StringHelper.Renderer<AttributeConverterDescriptor>() {
@Override
public String render(AttributeConverterDescriptor value) {
return value.getAttributeConverter().getClass().getName();
}
};
}

View File

@ -0,0 +1,37 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.boot.internal;
import com.fasterxml.classmate.MemberResolver;
import com.fasterxml.classmate.TypeResolver;
/**
* @author Steve Ebersole
*/
public class ClassmateContext {
private TypeResolver typeResolver = new TypeResolver();
private MemberResolver memberResolver = new MemberResolver( typeResolver );
public TypeResolver getTypeResolver() {
if ( typeResolver == null ) {
throw new IllegalStateException( "Classmate context has been released" );
}
return typeResolver;
}
public MemberResolver getMemberResolver() {
if ( memberResolver == null ) {
throw new IllegalStateException( "Classmate context has been released" );
}
return memberResolver;
}
public void release() {
typeResolver = null;
memberResolver = null;
}
}

View File

@ -48,6 +48,8 @@ import org.hibernate.boot.model.relational.Namespace;
import org.hibernate.boot.model.source.internal.ConstraintSecondPass; import org.hibernate.boot.model.source.internal.ConstraintSecondPass;
import org.hibernate.boot.model.source.internal.ImplicitColumnNamingSecondPass; import org.hibernate.boot.model.source.internal.ImplicitColumnNamingSecondPass;
import org.hibernate.boot.model.source.spi.LocalMetadataBuildingContext; import org.hibernate.boot.model.source.spi.LocalMetadataBuildingContext;
import org.hibernate.boot.spi.AttributeConverterAutoApplyHandler;
import org.hibernate.boot.spi.AttributeConverterDescriptor;
import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.MetadataBuildingOptions; import org.hibernate.boot.spi.MetadataBuildingOptions;
@ -115,6 +117,9 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
private final MetadataBuildingOptions options; private final MetadataBuildingOptions options;
private final TypeResolver typeResolver; private final TypeResolver typeResolver;
private final AttributeConverterManager attributeConverterManager = new AttributeConverterManager();
private final ClassmateContext classmateContext = new ClassmateContext();
private final UUID uuid; private final UUID uuid;
private final MutableIdentifierGeneratorFactory identifierGeneratorFactory; private final MutableIdentifierGeneratorFactory identifierGeneratorFactory;
@ -136,10 +141,8 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
private final Map<String, FetchProfile> fetchProfileMap = new HashMap<String, FetchProfile>(); private final Map<String, FetchProfile> fetchProfileMap = new HashMap<String, FetchProfile>();
private final Map<String, IdentifierGeneratorDefinition> idGeneratorDefinitionMap = new HashMap<String, IdentifierGeneratorDefinition>(); private final Map<String, IdentifierGeneratorDefinition> idGeneratorDefinitionMap = new HashMap<String, IdentifierGeneratorDefinition>();
private Map<Class, AttributeConverterDefinition> attributeConverterDefinitionsByClass;
private Map<String, SQLFunction> sqlFunctionMap; private Map<String, SQLFunction> sqlFunctionMap;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// All the annotation-processing-specific state :( // All the annotation-processing-specific state :(
private final Set<String> defaultIdentifierGeneratorNames = new HashSet<String>(); private final Set<String> defaultIdentifierGeneratorNames = new HashSet<String>();
@ -335,68 +338,33 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
} }
} }
@Override
public ClassmateContext getClassmateContext() {
return classmateContext;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// attribute converters // attribute converters
@Override @Override
public void addAttributeConverter(AttributeConverterDefinition definition) { public void addAttributeConverter(AttributeConverterDefinition definition) {
if ( attributeConverterDefinitionsByClass == null ) { attributeConverterManager.addConverter(
attributeConverterDefinitionsByClass = new ConcurrentHashMap<Class, AttributeConverterDefinition>(); AttributeConverterDescriptorImpl.create(
} definition,
classmateContext
final Object old = attributeConverterDefinitionsByClass.put(
definition.getAttributeConverter().getClass(),
definition
);
if ( old != null ) {
throw new AssertionFailure(
String.format(
Locale.ENGLISH,
"AttributeConverter class [%s] registered multiple times",
definition.getAttributeConverter().getClass()
)
);
}
}
public static AttributeConverter instantiateAttributeConverter(Class<? extends AttributeConverter> attributeConverterClass) {
try {
return attributeConverterClass.newInstance();
}
catch (Exception e) {
throw new AnnotationException(
"Unable to instantiate AttributeConverter [" + attributeConverterClass.getName() + "]",
e
);
}
}
public static AttributeConverterDefinition toAttributeConverterDefinition(AttributeConverter attributeConverter) {
boolean autoApply = false;
Converter converterAnnotation = attributeConverter.getClass().getAnnotation( Converter.class );
if ( converterAnnotation != null ) {
autoApply = converterAnnotation.autoApply();
}
return new AttributeConverterDefinition( attributeConverter, autoApply );
}
@Override
public void addAttributeConverter(Class<? extends AttributeConverter> converterClass) {
addAttributeConverter(
toAttributeConverterDefinition(
instantiateAttributeConverter( converterClass )
) )
); );
} }
@Override @Override
public java.util.Collection<AttributeConverterDefinition> getAttributeConverters() { public void addAttributeConverter(Class<? extends AttributeConverter> converterClass) {
if ( attributeConverterDefinitionsByClass == null ) { addAttributeConverter( AttributeConverterDefinition.from( converterClass ) );
return Collections.emptyList(); }
}
return attributeConverterDefinitionsByClass.values(); @Override
public AttributeConverterAutoApplyHandler getAttributeConverterAutoApplyHandler() {
return attributeConverterManager;
} }
@ -2164,27 +2132,32 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
processSecondPasses( buildingContext ); processSecondPasses( buildingContext );
processExportableProducers( buildingContext ); processExportableProducers( buildingContext );
return new MetadataImpl( try {
uuid, return new MetadataImpl(
options, uuid,
typeResolver, options,
identifierGeneratorFactory, typeResolver,
entityBindingMap, identifierGeneratorFactory,
mappedSuperClasses, entityBindingMap,
collectionBindingMap, mappedSuperClasses,
typeDefinitionMap, collectionBindingMap,
filterDefinitionMap, typeDefinitionMap,
fetchProfileMap, filterDefinitionMap,
imports, fetchProfileMap,
idGeneratorDefinitionMap, imports,
namedQueryMap, idGeneratorDefinitionMap,
namedNativeQueryMap, namedQueryMap,
namedProcedureCallMap, namedNativeQueryMap,
sqlResultSetMappingMap, namedProcedureCallMap,
namedEntityGraphMap, sqlResultSetMappingMap,
sqlFunctionMap, namedEntityGraphMap,
getDatabase() sqlFunctionMap,
); getDatabase()
);
}
finally {
classmateContext.release();
}
} }
private void processExportableProducers(MetadataBuildingContext buildingContext) { private void processExportableProducers(MetadataBuildingContext buildingContext) {

View File

@ -0,0 +1,18 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.boot.spi;
import org.hibernate.annotations.common.reflection.XProperty;
/**
* @author Steve Ebersole
*/
public interface AttributeConverterAutoApplyHandler {
AttributeConverterDescriptor findAutoApplyConverterForAttribute(XProperty xProperty, MetadataBuildingContext context);
AttributeConverterDescriptor findAutoApplyConverterForCollectionElement(XProperty xProperty, MetadataBuildingContext context);
AttributeConverterDescriptor findAutoApplyConverterForMapKey(XProperty xProperty, MetadataBuildingContext context);
}

View File

@ -0,0 +1,27 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.boot.spi;
import javax.persistence.AttributeConverter;
import org.hibernate.annotations.common.reflection.XProperty;
/**
* Internal descriptor for an AttributeConverter implementation.
*
* @author Steve Ebersole
*/
public interface AttributeConverterDescriptor {
AttributeConverter getAttributeConverter();
Class<?> getDomainType();
Class<?> getJdbcType();
boolean shouldAutoApplyToAttribute(XProperty xProperty, MetadataBuildingContext context);
boolean shouldAutoApplyToCollectionElement(XProperty xProperty, MetadataBuildingContext context);
boolean shouldAutoApplyToMapKey(XProperty xProperty, MetadataBuildingContext context);
}

View File

@ -17,6 +17,7 @@ import org.hibernate.HibernateException;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.annotations.AnyMetaDef; import org.hibernate.annotations.AnyMetaDef;
import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.boot.internal.ClassmateContext;
import org.hibernate.boot.model.IdentifierGeneratorDefinition; import org.hibernate.boot.model.IdentifierGeneratorDefinition;
import org.hibernate.boot.model.TypeDefinition; import org.hibernate.boot.model.TypeDefinition;
import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.Identifier;
@ -213,7 +214,9 @@ public interface InFlightMetadataCollector extends Mapping, MetadataImplementor
void addAttributeConverter(AttributeConverterDefinition converter); void addAttributeConverter(AttributeConverterDefinition converter);
void addAttributeConverter(Class<? extends AttributeConverter> converterClass); void addAttributeConverter(Class<? extends AttributeConverter> converterClass);
java.util.Collection<AttributeConverterDefinition> getAttributeConverters();
AttributeConverterAutoApplyHandler getAttributeConverterAutoApplyHandler();
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// second passes // second passes
@ -275,6 +278,8 @@ public interface InFlightMetadataCollector extends Mapping, MetadataImplementor
NaturalIdUniqueKeyBinder locateNaturalIdUniqueKeyBinder(String entityName); NaturalIdUniqueKeyBinder locateNaturalIdUniqueKeyBinder(String entityName);
void registerNaturalIdUniqueKeyBinder(String entityName, NaturalIdUniqueKeyBinder ukBinder); void registerNaturalIdUniqueKeyBinder(String entityName, NaturalIdUniqueKeyBinder ukBinder);
ClassmateContext getClassmateContext();
interface DelayedPropertyReferenceHandler extends Serializable { interface DelayedPropertyReferenceHandler extends Serializable {
void process(InFlightMetadataCollector metadataCollector); void process(InFlightMetadataCollector metadataCollector);
} }

View File

@ -24,6 +24,8 @@ import org.hibernate.AssertionFailure;
import org.hibernate.annotations.common.reflection.XAnnotatedElement; import org.hibernate.annotations.common.reflection.XAnnotatedElement;
import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.internal.AttributeConverterDescriptorImpl;
import org.hibernate.boot.spi.AttributeConverterDescriptor;
import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.StringHelper;
@ -68,7 +70,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
protected abstract AttributeConversionInfo locateAttributeConversionInfo(String path); protected abstract AttributeConversionInfo locateAttributeConversionInfo(String path);
@Override @Override
public AttributeConverterDefinition resolveAttributeConverterDefinition(XProperty property) { public AttributeConverterDescriptor resolveAttributeConverterDescriptor(XProperty property) {
AttributeConversionInfo info = locateAttributeConversionInfo( property ); AttributeConversionInfo info = locateAttributeConversionInfo( property );
if ( info != null ) { if ( info != null ) {
if ( info.isConversionDisabled() ) { if ( info.isConversionDisabled() ) {
@ -76,7 +78,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
} }
else { else {
try { try {
return makeAttributeConverterDefinition( info ); return makeAttributeConverterDescriptor( info );
} }
catch (Exception e) { catch (Exception e) {
throw new IllegalStateException( throw new IllegalStateException(
@ -89,28 +91,15 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
log.debugf( "Attempting to locate auto-apply AttributeConverter for property [%s:%s]", path, property.getName() ); log.debugf( "Attempting to locate auto-apply AttributeConverter for property [%s:%s]", path, property.getName() );
final Class propertyType = context.getBuildingOptions().getReflectionManager().toClass( property.getType() ); return context.getMetadataCollector()
for ( AttributeConverterDefinition attributeConverterDefinition : context.getMetadataCollector().getAttributeConverters() ) { .getAttributeConverterAutoApplyHandler()
if ( ! attributeConverterDefinition.isAutoApply() ) { .findAutoApplyConverterForAttribute( property, context );
continue;
}
log.debugf(
"Checking auto-apply AttributeConverter [%s] type [%s] for match [%s]",
attributeConverterDefinition.toString(),
attributeConverterDefinition.getEntityAttributeType().getSimpleName(),
propertyType.getSimpleName()
);
if ( areTypeMatch( attributeConverterDefinition.getEntityAttributeType(), propertyType ) ) {
return attributeConverterDefinition;
}
}
return null;
} }
protected AttributeConverterDefinition makeAttributeConverterDefinition(AttributeConversionInfo conversion) { protected AttributeConverterDescriptor makeAttributeConverterDescriptor(AttributeConversionInfo conversion) {
try { try {
return new AttributeConverterDefinition( conversion.getConverterClass().newInstance(), false ); AttributeConverterDefinition definition = new AttributeConverterDefinition( conversion.getConverterClass().newInstance(), false );
return AttributeConverterDescriptorImpl.create( definition, context.getMetadataCollector().getClassmateContext() );
} }
catch (Exception e) { catch (Exception e) {
throw new AnnotationException( "Unable to create AttributeConverter instance", e ); throw new AnnotationException( "Unable to create AttributeConverter instance", e );

View File

@ -12,20 +12,20 @@ import java.lang.reflect.TypeVariable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.persistence.AttributeConverter; import javax.persistence.AttributeConverter;
import javax.persistence.Converter; import javax.persistence.Converter;
import org.hibernate.AnnotationException; import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure; import org.hibernate.AssertionFailure;
import org.jboss.logging.Logger;
/** /**
* Externalized representation of an AttributeConverter
*
* @author Steve Ebersole * @author Steve Ebersole
*
* @see org.hibernate.boot.spi.AttributeConverterDescriptor
*/ */
public class AttributeConverterDefinition { public class AttributeConverterDefinition {
private static final Logger log = Logger.getLogger( AttributeConverterDefinition.class );
private final AttributeConverter attributeConverter; private final AttributeConverter attributeConverter;
private final boolean autoApply; private final boolean autoApply;
private final Class entityAttributeType; private final Class entityAttributeType;

View File

@ -25,7 +25,7 @@ import org.hibernate.annotations.ManyToAny;
import org.hibernate.annotations.MapKeyType; import org.hibernate.annotations.MapKeyType;
import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.model.source.spi.AttributePath; import org.hibernate.boot.spi.AttributeConverterDescriptor;
import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
@ -345,7 +345,7 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
} }
} }
public AttributeConverterDefinition resolveElementAttributeConverterDefinition(XClass elementXClass) { public AttributeConverterDescriptor resolveElementAttributeConverterDescriptor(XProperty collectionXProperty, XClass elementXClass) {
AttributeConversionInfo info = locateAttributeConversionInfo( "element" ); AttributeConversionInfo info = locateAttributeConversionInfo( "element" );
if ( info != null ) { if ( info != null ) {
if ( info.isConversionDisabled() ) { if ( info.isConversionDisabled() ) {
@ -353,7 +353,7 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
} }
else { else {
try { try {
return makeAttributeConverterDefinition( info ); return makeAttributeConverterDescriptor( info );
} }
catch (Exception e) { catch (Exception e) {
throw new IllegalStateException( throw new IllegalStateException(
@ -369,25 +369,11 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
collection.getRole() collection.getRole()
); );
final Class elementClass = determineElementClass( elementXClass ); // todo : do we need to pass along `XClass elementXClass`?
if ( elementClass != null ) {
for ( AttributeConverterDefinition attributeConverterDefinition : getContext().getMetadataCollector().getAttributeConverters() ) {
if ( ! attributeConverterDefinition.isAutoApply() ) {
continue;
}
log.debugf(
"Checking auto-apply AttributeConverter [%s] type [%s] for match [%s]",
attributeConverterDefinition.toString(),
attributeConverterDefinition.getEntityAttributeType().getSimpleName(),
elementClass.getSimpleName()
);
if ( areTypeMatch( attributeConverterDefinition.getEntityAttributeType(), elementClass ) ) {
return attributeConverterDefinition;
}
}
}
return null; return getContext().getMetadataCollector()
.getAttributeConverterAutoApplyHandler()
.findAutoApplyConverterForCollectionElement( collectionXProperty, getContext() );
} }
private Class determineElementClass(XClass elementXClass) { private Class determineElementClass(XClass elementXClass) {
@ -419,7 +405,7 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
return null; return null;
} }
public AttributeConverterDefinition keyElementAttributeConverterDefinition(XClass keyXClass) { public AttributeConverterDescriptor mapKeyAttributeConverterDescriptor(XProperty mapXProperty, XClass keyXClass) {
AttributeConversionInfo info = locateAttributeConversionInfo( "key" ); AttributeConversionInfo info = locateAttributeConversionInfo( "key" );
if ( info != null ) { if ( info != null ) {
if ( info.isConversionDisabled() ) { if ( info.isConversionDisabled() ) {
@ -427,7 +413,7 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
} }
else { else {
try { try {
return makeAttributeConverterDefinition( info ); return makeAttributeConverterDescriptor( info );
} }
catch (Exception e) { catch (Exception e) {
throw new IllegalStateException( throw new IllegalStateException(
@ -443,25 +429,11 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
collection.getRole() collection.getRole()
); );
final Class elementClass = determineKeyClass( keyXClass ); // todo : do we need to pass along `XClass keyXClass`?
if ( elementClass != null ) {
for ( AttributeConverterDefinition attributeConverterDefinition : getContext().getMetadataCollector().getAttributeConverters() ) {
if ( ! attributeConverterDefinition.isAutoApply() ) {
continue;
}
log.debugf(
"Checking auto-apply AttributeConverter [%s] type [%s] for match [%s]",
attributeConverterDefinition.toString(),
attributeConverterDefinition.getEntityAttributeType().getSimpleName(),
elementClass.getSimpleName()
);
if ( areTypeMatch( attributeConverterDefinition.getEntityAttributeType(), elementClass ) ) {
return attributeConverterDefinition;
}
}
}
return null; return getContext().getMetadataCollector()
.getAttributeConverterAutoApplyHandler()
.findAutoApplyConverterForMapKey( mapXProperty, getContext() );
} }
private Class determineKeyClass(XClass keyXClass) { private Class determineKeyClass(XClass keyXClass) {

View File

@ -12,6 +12,7 @@ import javax.persistence.JoinTable;
import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.spi.AttributeConverterDescriptor;
import org.hibernate.mapping.Join; import org.hibernate.mapping.Join;
import org.hibernate.mapping.KeyValue; import org.hibernate.mapping.KeyValue;
import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.PersistentClass;
@ -91,5 +92,5 @@ public interface PropertyHolder {
* @param property * @param property
* @return * @return
*/ */
AttributeConverterDefinition resolveAttributeConverterDefinition(XProperty property); AttributeConverterDescriptor resolveAttributeConverterDescriptor(XProperty property);
} }

View File

@ -1460,7 +1460,7 @@ public abstract class CollectionBinder {
property, property,
elementClass, elementClass,
collValue.getOwnerEntityName(), collValue.getOwnerEntityName(),
holder.resolveElementAttributeConverterDefinition( elementClass ) holder.resolveElementAttributeConverterDescriptor( property, elementClass )
); );
elementBinder.setPersistentClassName( propertyHolder.getEntityName() ); elementBinder.setPersistentClassName( propertyHolder.getEntityName() );
elementBinder.setAccessType( accessType ); elementBinder.setAccessType( accessType );

View File

@ -275,7 +275,7 @@ public class MapBinder extends CollectionBinder {
property, property,
keyXClass, keyXClass,
this.collection.getOwnerEntityName(), this.collection.getOwnerEntityName(),
holder.keyElementAttributeConverterDefinition( keyXClass ) holder.mapKeyAttributeConverterDescriptor( property, keyXClass )
); );
elementBinder.setPersistentClassName( propertyHolder.getEntityName() ); elementBinder.setPersistentClassName( propertyHolder.getEntityName() );
elementBinder.setAccessType( accessType ); elementBinder.setAccessType( accessType );

View File

@ -180,7 +180,7 @@ public class PropertyBinder {
property, property,
returnedClass, returnedClass,
containerClassName, containerClassName,
holder.resolveAttributeConverterDefinition( property ) holder.resolveAttributeConverterDescriptor( property )
); );
simpleValueBinder.setReferencedEntityName( referencedEntityName ); simpleValueBinder.setReferencedEntityName( referencedEntityName );
simpleValueBinder.setAccessType( accessType ); simpleValueBinder.setAccessType( accessType );

View File

@ -31,6 +31,7 @@ import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.annotations.common.util.StandardClassLoaderDelegateImpl; import org.hibernate.annotations.common.util.StandardClassLoaderDelegateImpl;
import org.hibernate.boot.model.TypeDefinition; import org.hibernate.boot.model.TypeDefinition;
import org.hibernate.boot.spi.AttributeConverterDescriptor;
import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.cfg.AccessType; import org.hibernate.cfg.AccessType;
import org.hibernate.cfg.AttributeConverterDefinition; import org.hibernate.cfg.AttributeConverterDefinition;
@ -86,7 +87,7 @@ public class SimpleValueBinder {
private XProperty xproperty; private XProperty xproperty;
private AccessType accessType; private AccessType accessType;
private AttributeConverterDefinition attributeConverterDefinition; private AttributeConverterDescriptor attributeConverterDescriptor;
public void setReferencedEntityName(String referencedEntityName) { public void setReferencedEntityName(String referencedEntityName) {
this.referencedEntityName = referencedEntityName; this.referencedEntityName = referencedEntityName;
@ -131,7 +132,7 @@ public class SimpleValueBinder {
//TODO execute it lazily to be order safe //TODO execute it lazily to be order safe
public void setType(XProperty property, XClass returnedClass, String declaringClassName, AttributeConverterDefinition attributeConverterDefinition) { public void setType(XProperty property, XClass returnedClass, String declaringClassName, AttributeConverterDescriptor attributeConverterDescriptor) {
if ( returnedClass == null ) { if ( returnedClass == null ) {
// we cannot guess anything // we cannot guess anything
return; return;
@ -302,11 +303,11 @@ public class SimpleValueBinder {
defaultType = BinderHelper.isEmptyAnnotationValue( type ) ? returnedClassName : type; defaultType = BinderHelper.isEmptyAnnotationValue( type ) ? returnedClassName : type;
this.typeParameters = typeParameters; this.typeParameters = typeParameters;
applyAttributeConverter( property, attributeConverterDefinition ); applyAttributeConverter( property, attributeConverterDescriptor );
} }
private void applyAttributeConverter(XProperty property, AttributeConverterDefinition attributeConverterDefinition) { private void applyAttributeConverter(XProperty property, AttributeConverterDescriptor attributeConverterDescriptor) {
if ( attributeConverterDefinition == null ) { if ( attributeConverterDescriptor == null ) {
return; return;
} }
@ -337,7 +338,7 @@ public class SimpleValueBinder {
return; return;
} }
this.attributeConverterDefinition = attributeConverterDefinition; this.attributeConverterDescriptor = attributeConverterDescriptor;
} }
private boolean isAssociation() { private boolean isAssociation() {
@ -429,7 +430,7 @@ public class SimpleValueBinder {
public void fillSimpleValue() { public void fillSimpleValue() {
LOG.debugf( "Starting fillSimpleValue for %s", propertyName ); LOG.debugf( "Starting fillSimpleValue for %s", propertyName );
if ( attributeConverterDefinition != null ) { if ( attributeConverterDescriptor != null ) {
if ( ! BinderHelper.isEmptyAnnotationValue( explicitType ) ) { if ( ! BinderHelper.isEmptyAnnotationValue( explicitType ) ) {
throw new AnnotationException( throw new AnnotationException(
String.format( String.format(
@ -442,11 +443,11 @@ public class SimpleValueBinder {
} }
LOG.debugf( LOG.debugf(
"Applying JPA AttributeConverter [%s] to [%s:%s]", "Applying JPA AttributeConverter [%s] to [%s:%s]",
attributeConverterDefinition, attributeConverterDescriptor,
persistentClassName, persistentClassName,
propertyName propertyName
); );
simpleValue.setJpaAttributeConverterDefinition( attributeConverterDefinition ); simpleValue.setJpaAttributeConverterDescriptor( attributeConverterDescriptor );
} }
else { else {
String type; String type;
@ -480,7 +481,7 @@ public class SimpleValueBinder {
simpleValue.setTypeName( type ); simpleValue.setTypeName( type );
} }
if ( persistentClassName != null || attributeConverterDefinition != null ) { if ( persistentClassName != null || attributeConverterDescriptor != null ) {
try { try {
simpleValue.setTypeUsingReflection( persistentClassName, propertyName ); simpleValue.setTypeUsingReflection( persistentClassName, propertyName );
} }

View File

@ -13,6 +13,7 @@ import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.BitSet; import java.util.BitSet;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -803,4 +804,29 @@ public final class StringHelper {
public static List<String> parseCommaSeparatedString(String incomingString) { public static List<String> parseCommaSeparatedString(String incomingString) {
return Arrays.asList( incomingString.split( "\\s*,\\s*" ) ); return Arrays.asList( incomingString.split( "\\s*,\\s*" ) );
} }
public static <T> String join(Collection<T> values, Renderer<T> renderer) {
final StringBuilder buffer = new StringBuilder();
boolean firstPass = true;
for ( T value : values ) {
if ( firstPass ) {
firstPass = false;
}
else {
buffer.append( ", " );
}
buffer.append( renderer.render( value ) );
}
return buffer.toString();
}
public static <T> String join(T[] values, Renderer<T> renderer) {
return join( Arrays.asList( values ), renderer );
}
public interface Renderer<T> {
String render(T value);
}
} }

View File

@ -19,10 +19,11 @@ import javax.persistence.AttributeConverter;
import org.hibernate.FetchMode; import org.hibernate.FetchMode;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.internal.AttributeConverterDescriptorNonAutoApplicableImpl;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
import org.hibernate.boot.spi.AttributeConverterDescriptor;
import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.cfg.AttributeConverterDefinition;
import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.ConfigurationService;
@ -75,7 +76,7 @@ public class SimpleValue implements KeyValue {
private boolean alternateUniqueKey; private boolean alternateUniqueKey;
private boolean cascadeDeleteEnabled; private boolean cascadeDeleteEnabled;
private AttributeConverterDefinition attributeConverterDefinition; private AttributeConverterDescriptor attributeConverterDescriptor;
private Type type; private Type type;
public SimpleValue(MetadataImplementor metadata) { public SimpleValue(MetadataImplementor metadata) {
@ -155,7 +156,7 @@ public class SimpleValue implements KeyValue {
.getService( ClassLoaderService.class ); .getService( ClassLoaderService.class );
try { try {
final Class<AttributeConverter> converterClass = cls.classForName( converterClassName ); final Class<AttributeConverter> converterClass = cls.classForName( converterClassName );
attributeConverterDefinition = new AttributeConverterDefinition( converterClass.newInstance(), false ); attributeConverterDescriptor = new AttributeConverterDescriptorNonAutoApplicableImpl( converterClass.newInstance() );
return; return;
} }
catch (Exception e) { catch (Exception e) {
@ -420,7 +421,7 @@ public class SimpleValue implements KeyValue {
return; return;
} }
if ( attributeConverterDefinition == null ) { if ( attributeConverterDescriptor == null ) {
// this is here to work like legacy. This should change when we integrate with metamodel to // this is here to work like legacy. This should change when we integrate with metamodel to
// look for SqlTypeDescriptor and JavaTypeDescriptor individually and create the BasicType (well, really // look for SqlTypeDescriptor and JavaTypeDescriptor individually and create the BasicType (well, really
// keep a registry of [SqlTypeDescriptor,JavaTypeDescriptor] -> BasicType...) // keep a registry of [SqlTypeDescriptor,JavaTypeDescriptor] -> BasicType...)
@ -475,8 +476,8 @@ public class SimpleValue implements KeyValue {
private Type buildAttributeConverterTypeAdapter() { private Type buildAttributeConverterTypeAdapter() {
// todo : validate the number of columns present here? // todo : validate the number of columns present here?
final Class entityAttributeJavaType = attributeConverterDefinition.getEntityAttributeType(); final Class entityAttributeJavaType = attributeConverterDescriptor.getDomainType();
final Class databaseColumnJavaType = attributeConverterDefinition.getDatabaseColumnType(); final Class databaseColumnJavaType = attributeConverterDescriptor.getJdbcType();
// resolve the JavaTypeDescriptor ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // resolve the JavaTypeDescriptor ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -523,14 +524,14 @@ public class SimpleValue implements KeyValue {
// and finally construct the adapter, which injects the AttributeConverter calls into the binding/extraction // and finally construct the adapter, which injects the AttributeConverter calls into the binding/extraction
// process... // process...
final SqlTypeDescriptor sqlTypeDescriptorAdapter = new AttributeConverterSqlTypeDescriptorAdapter( final SqlTypeDescriptor sqlTypeDescriptorAdapter = new AttributeConverterSqlTypeDescriptorAdapter(
attributeConverterDefinition.getAttributeConverter(), attributeConverterDescriptor.getAttributeConverter(),
sqlTypeDescriptor, sqlTypeDescriptor,
intermediateJavaTypeDescriptor intermediateJavaTypeDescriptor
); );
// todo : cache the AttributeConverterTypeAdapter in case that AttributeConverter is applied multiple times. // todo : cache the AttributeConverterTypeAdapter in case that AttributeConverter is applied multiple times.
final String name = AttributeConverterTypeAdapter.NAME_PREFIX + attributeConverterDefinition.getAttributeConverter().getClass().getName(); final String name = AttributeConverterTypeAdapter.NAME_PREFIX + attributeConverterDescriptor.getAttributeConverter().getClass().getName();
final String description = String.format( final String description = String.format(
"BasicType adapter for AttributeConverter<%s,%s>", "BasicType adapter for AttributeConverter<%s,%s>",
entityAttributeJavaType.getSimpleName(), entityAttributeJavaType.getSimpleName(),
@ -539,7 +540,7 @@ public class SimpleValue implements KeyValue {
return new AttributeConverterTypeAdapter( return new AttributeConverterTypeAdapter(
name, name,
description, description,
attributeConverterDefinition.getAttributeConverter(), attributeConverterDescriptor.getAttributeConverter(),
sqlTypeDescriptorAdapter, sqlTypeDescriptorAdapter,
entityAttributeJavaType, entityAttributeJavaType,
databaseColumnJavaType, databaseColumnJavaType,
@ -583,8 +584,8 @@ public class SimpleValue implements KeyValue {
return getColumnInsertability(); return getColumnInsertability();
} }
public void setJpaAttributeConverterDefinition(AttributeConverterDefinition attributeConverterDefinition) { public void setJpaAttributeConverterDescriptor(AttributeConverterDescriptor attributeConverterDescriptor) {
this.attributeConverterDefinition = attributeConverterDefinition; this.attributeConverterDescriptor = attributeConverterDescriptor;
} }
private void createParameterImpl() { private void createParameterImpl() {

View File

@ -22,6 +22,7 @@ import org.hibernate.IrrelevantEntity;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
import org.hibernate.boot.MetadataSources; import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.internal.AttributeConverterDescriptorNonAutoApplicableImpl;
import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.boot.spi.MetadataImplementor;
@ -96,8 +97,8 @@ public class AttributeConverterTest extends BaseUnitTestCase {
try { try {
MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr ).buildMetadata(); MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr ).buildMetadata();
SimpleValue simpleValue = new SimpleValue( metadata ); SimpleValue simpleValue = new SimpleValue( metadata );
simpleValue.setJpaAttributeConverterDefinition( simpleValue.setJpaAttributeConverterDescriptor(
new AttributeConverterDefinition( new StringClobConverter(), true ) new AttributeConverterDescriptorNonAutoApplicableImpl( new StringClobConverter() )
); );
simpleValue.setTypeUsingReflection( IrrelevantEntity.class.getName(), "name" ); simpleValue.setTypeUsingReflection( IrrelevantEntity.class.getName(), "name" );

View File

@ -8,14 +8,29 @@ package org.hibernate.test.converter.generics;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.StringTokenizer;
import javax.persistence.AttributeConverter; import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.AttributeConverterDefinition; import org.hibernate.cfg.AttributeConverterDefinition;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.type.descriptor.converter.AttributeConverterTypeAdapter;
import org.hibernate.testing.TestForIssue; import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseUnitTestCase; import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
/** /**
@ -27,6 +42,20 @@ import static org.junit.Assert.assertEquals;
*/ */
public class ParameterizedAttributeConverterParameterTypeTest extends BaseUnitTestCase { public class ParameterizedAttributeConverterParameterTypeTest extends BaseUnitTestCase {
private static StandardServiceRegistry ssr;
@BeforeClass
public static void beforeClass() {
ssr = new StandardServiceRegistryBuilder().build();
}
@AfterClass
public static void afterClass() {
if ( ssr != null ) {
StandardServiceRegistryBuilder.destroy( ssr );
}
}
public static class CustomAttributeConverter implements AttributeConverter<List<String>, Integer> { public static class CustomAttributeConverter implements AttributeConverter<List<String>, Integer> {
@Override @Override
public Integer convertToDatabaseColumn(List<String> attribute) { public Integer convertToDatabaseColumn(List<String> attribute) {
@ -46,4 +75,119 @@ public class ParameterizedAttributeConverterParameterTypeTest extends BaseUnitTe
assertEquals( List.class, def.getEntityAttributeType() ); assertEquals( List.class, def.getEntityAttributeType() );
} }
@Test
@TestForIssue( jiraKey = "HHH-10050" )
public void testNestedTypeParameterAutoApplication() {
final Metadata metadata = new MetadataSources( ssr )
.addAnnotatedClass( SampleEntity.class )
.getMetadataBuilder()
.applyAttributeConverter( IntegerListConverter.class )
.applyAttributeConverter( StringListConverter.class )
.build();
// lets make sure the auto-apply converters were applied properly...
PersistentClass pc = metadata.getEntityBinding( SampleEntity.class.getName() );
{
Property prop = pc.getProperty( "someStrings" );
AttributeConverterTypeAdapter type = assertTyping(
AttributeConverterTypeAdapter.class,
prop.getType()
);
assertTyping(
StringListConverter.class,
type.getAttributeConverter()
);
}
{
Property prop = pc.getProperty( "someIntegers" );
AttributeConverterTypeAdapter type = assertTyping(
AttributeConverterTypeAdapter.class,
prop.getType()
);
assertTyping(
IntegerListConverter.class,
type.getAttributeConverter()
);
}
}
@Entity
public static class SampleEntity {
@Id
private Integer id;
private List<String> someStrings;
private List<Integer> someIntegers;
}
@Converter( autoApply = true )
public static class IntegerListConverter implements AttributeConverter<List<Integer>,String> {
@Override
public String convertToDatabaseColumn(List<Integer> attribute) {
if ( attribute == null || attribute.isEmpty() ) {
return null;
}
else {
return StringHelper.join( ", ", attribute );
}
}
@Override
public List<Integer> convertToEntityAttribute(String dbData) {
if ( dbData == null ) {
return null;
}
dbData = dbData.trim();
if ( dbData.length() == 0 ) {
return null;
}
final List<Integer> integers = new ArrayList<Integer>();
final StringTokenizer tokens = new StringTokenizer( dbData, "," );
while ( tokens.hasMoreTokens() ) {
integers.add( Integer.valueOf( tokens.nextToken() ) );
}
return integers;
}
}
@Converter( autoApply = true )
public static class StringListConverter implements AttributeConverter<List<String>,String> {
@Override
public String convertToDatabaseColumn(List<String> attribute) {
if ( attribute == null || attribute.isEmpty() ) {
return null;
}
else {
return StringHelper.join( ", ", attribute );
}
}
@Override
public List<String> convertToEntityAttribute(String dbData) {
if ( dbData == null ) {
return null;
}
dbData = dbData.trim();
if ( dbData.length() == 0 ) {
return null;
}
final List<String> strings = new ArrayList<String>();
final StringTokenizer tokens = new StringTokenizer( dbData, "," );
while ( tokens.hasMoreTokens() ) {
strings.add( tokens.nextToken() );
}
return strings;
}
}
} }

View File

@ -27,7 +27,7 @@ ext {
// Annotations // Annotations
commons_annotations: 'org.hibernate.common:hibernate-commons-annotations:5.0.0.Final', commons_annotations: 'org.hibernate.common:hibernate-commons-annotations:5.0.0.Final',
jandex: 'org.jboss:jandex:2.0.0.CR1', jandex: 'org.jboss:jandex:2.0.0.CR1',
classmate: 'com.fasterxml:classmate:0.8.0', classmate: 'com.fasterxml:classmate:1.3.0',
// Woodstox // Woodstox
woodstox: "org.codehaus.woodstox:woodstox-core-asl:4.3.0", woodstox: "org.codehaus.woodstox:woodstox-core-asl:4.3.0",