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 @Override
public void addAttributeConverter(Class<? extends AttributeConverter> converterClass) { public void addAttributeConverter(Class<? extends AttributeConverter> converterClass) {
addAttributeConverter( addAttributeConverter( AttributeConverterDefinition.from( converterClass ) );
toAttributeConverterDefinition(
instantiateAttributeConverter( converterClass )
)
);
} }
@Override @Override
public java.util.Collection<AttributeConverterDefinition> getAttributeConverters() { public AttributeConverterAutoApplyHandler getAttributeConverterAutoApplyHandler() {
if ( attributeConverterDefinitionsByClass == null ) { return attributeConverterManager;
return Collections.emptyList();
}
return attributeConverterDefinitionsByClass.values();
} }
@ -2164,6 +2132,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
processSecondPasses( buildingContext ); processSecondPasses( buildingContext );
processExportableProducers( buildingContext ); processExportableProducers( buildingContext );
try {
return new MetadataImpl( return new MetadataImpl(
uuid, uuid,
options, options,
@ -2186,6 +2155,10 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
getDatabase() getDatabase()
); );
} }
finally {
classmateContext.release();
}
}
private void processExportableProducers(MetadataBuildingContext buildingContext) { private void processExportableProducers(MetadataBuildingContext buildingContext) {
// for now we only handle id generators as ExportableProducers // for now we only handle id generators as ExportableProducers

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 AttributeConverterDescriptor makeAttributeConverterDescriptor(AttributeConversionInfo conversion) {
}
protected AttributeConverterDefinition makeAttributeConverterDefinition(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",