HHH-10050 - AttributeConverter should supports ParameterizedType if autoApply is true
This commit is contained in:
parent
9633c0a6e5
commit
0cf66b85e0
|
@ -395,7 +395,6 @@ public interface MetadataBuilder {
|
|||
|
||||
MetadataBuilder applyAuxiliaryDatabaseObject(AuxiliaryDatabaseObject auxiliaryDatabaseObject);
|
||||
|
||||
|
||||
/**
|
||||
* Adds an AttributeConverter by a AttributeConverterDefinition
|
||||
*
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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.ImplicitColumnNamingSecondPass;
|
||||
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.MetadataBuildingContext;
|
||||
import org.hibernate.boot.spi.MetadataBuildingOptions;
|
||||
|
@ -115,6 +117,9 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
|
|||
private final MetadataBuildingOptions options;
|
||||
private final TypeResolver typeResolver;
|
||||
|
||||
private final AttributeConverterManager attributeConverterManager = new AttributeConverterManager();
|
||||
private final ClassmateContext classmateContext = new ClassmateContext();
|
||||
|
||||
private final UUID uuid;
|
||||
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, IdentifierGeneratorDefinition> idGeneratorDefinitionMap = new HashMap<String, IdentifierGeneratorDefinition>();
|
||||
|
||||
private Map<Class, AttributeConverterDefinition> attributeConverterDefinitionsByClass;
|
||||
private Map<String, SQLFunction> sqlFunctionMap;
|
||||
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// All the annotation-processing-specific state :(
|
||||
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
|
||||
|
||||
@Override
|
||||
public void addAttributeConverter(AttributeConverterDefinition definition) {
|
||||
if ( attributeConverterDefinitionsByClass == null ) {
|
||||
attributeConverterDefinitionsByClass = new ConcurrentHashMap<Class, AttributeConverterDefinition>();
|
||||
}
|
||||
|
||||
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 )
|
||||
attributeConverterManager.addConverter(
|
||||
AttributeConverterDescriptorImpl.create(
|
||||
definition,
|
||||
classmateContext
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.util.Collection<AttributeConverterDefinition> getAttributeConverters() {
|
||||
if ( attributeConverterDefinitionsByClass == null ) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return attributeConverterDefinitionsByClass.values();
|
||||
public void addAttributeConverter(Class<? extends AttributeConverter> converterClass) {
|
||||
addAttributeConverter( AttributeConverterDefinition.from( converterClass ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeConverterAutoApplyHandler getAttributeConverterAutoApplyHandler() {
|
||||
return attributeConverterManager;
|
||||
}
|
||||
|
||||
|
||||
|
@ -2164,27 +2132,32 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
|
|||
processSecondPasses( buildingContext );
|
||||
processExportableProducers( buildingContext );
|
||||
|
||||
return new MetadataImpl(
|
||||
uuid,
|
||||
options,
|
||||
typeResolver,
|
||||
identifierGeneratorFactory,
|
||||
entityBindingMap,
|
||||
mappedSuperClasses,
|
||||
collectionBindingMap,
|
||||
typeDefinitionMap,
|
||||
filterDefinitionMap,
|
||||
fetchProfileMap,
|
||||
imports,
|
||||
idGeneratorDefinitionMap,
|
||||
namedQueryMap,
|
||||
namedNativeQueryMap,
|
||||
namedProcedureCallMap,
|
||||
sqlResultSetMappingMap,
|
||||
namedEntityGraphMap,
|
||||
sqlFunctionMap,
|
||||
getDatabase()
|
||||
);
|
||||
try {
|
||||
return new MetadataImpl(
|
||||
uuid,
|
||||
options,
|
||||
typeResolver,
|
||||
identifierGeneratorFactory,
|
||||
entityBindingMap,
|
||||
mappedSuperClasses,
|
||||
collectionBindingMap,
|
||||
typeDefinitionMap,
|
||||
filterDefinitionMap,
|
||||
fetchProfileMap,
|
||||
imports,
|
||||
idGeneratorDefinitionMap,
|
||||
namedQueryMap,
|
||||
namedNativeQueryMap,
|
||||
namedProcedureCallMap,
|
||||
sqlResultSetMappingMap,
|
||||
namedEntityGraphMap,
|
||||
sqlFunctionMap,
|
||||
getDatabase()
|
||||
);
|
||||
}
|
||||
finally {
|
||||
classmateContext.release();
|
||||
}
|
||||
}
|
||||
|
||||
private void processExportableProducers(MetadataBuildingContext buildingContext) {
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -17,6 +17,7 @@ import org.hibernate.HibernateException;
|
|||
import org.hibernate.MappingException;
|
||||
import org.hibernate.annotations.AnyMetaDef;
|
||||
import org.hibernate.annotations.common.reflection.XClass;
|
||||
import org.hibernate.boot.internal.ClassmateContext;
|
||||
import org.hibernate.boot.model.IdentifierGeneratorDefinition;
|
||||
import org.hibernate.boot.model.TypeDefinition;
|
||||
import org.hibernate.boot.model.naming.Identifier;
|
||||
|
@ -213,7 +214,9 @@ public interface InFlightMetadataCollector extends Mapping, MetadataImplementor
|
|||
|
||||
void addAttributeConverter(AttributeConverterDefinition converter);
|
||||
void addAttributeConverter(Class<? extends AttributeConverter> converterClass);
|
||||
java.util.Collection<AttributeConverterDefinition> getAttributeConverters();
|
||||
|
||||
AttributeConverterAutoApplyHandler getAttributeConverterAutoApplyHandler();
|
||||
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// second passes
|
||||
|
@ -275,6 +278,8 @@ public interface InFlightMetadataCollector extends Mapping, MetadataImplementor
|
|||
NaturalIdUniqueKeyBinder locateNaturalIdUniqueKeyBinder(String entityName);
|
||||
void registerNaturalIdUniqueKeyBinder(String entityName, NaturalIdUniqueKeyBinder ukBinder);
|
||||
|
||||
ClassmateContext getClassmateContext();
|
||||
|
||||
interface DelayedPropertyReferenceHandler extends Serializable {
|
||||
void process(InFlightMetadataCollector metadataCollector);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ import org.hibernate.AssertionFailure;
|
|||
import org.hibernate.annotations.common.reflection.XAnnotatedElement;
|
||||
import org.hibernate.annotations.common.reflection.XClass;
|
||||
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.internal.CoreLogging;
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
|
@ -68,7 +70,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
|
|||
protected abstract AttributeConversionInfo locateAttributeConversionInfo(String path);
|
||||
|
||||
@Override
|
||||
public AttributeConverterDefinition resolveAttributeConverterDefinition(XProperty property) {
|
||||
public AttributeConverterDescriptor resolveAttributeConverterDescriptor(XProperty property) {
|
||||
AttributeConversionInfo info = locateAttributeConversionInfo( property );
|
||||
if ( info != null ) {
|
||||
if ( info.isConversionDisabled() ) {
|
||||
|
@ -76,7 +78,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
|
|||
}
|
||||
else {
|
||||
try {
|
||||
return makeAttributeConverterDefinition( info );
|
||||
return makeAttributeConverterDescriptor( info );
|
||||
}
|
||||
catch (Exception e) {
|
||||
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() );
|
||||
|
||||
final Class propertyType = context.getBuildingOptions().getReflectionManager().toClass( property.getType() );
|
||||
for ( AttributeConverterDefinition attributeConverterDefinition : context.getMetadataCollector().getAttributeConverters() ) {
|
||||
if ( ! attributeConverterDefinition.isAutoApply() ) {
|
||||
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;
|
||||
return context.getMetadataCollector()
|
||||
.getAttributeConverterAutoApplyHandler()
|
||||
.findAutoApplyConverterForAttribute( property, context );
|
||||
}
|
||||
|
||||
protected AttributeConverterDefinition makeAttributeConverterDefinition(AttributeConversionInfo conversion) {
|
||||
protected AttributeConverterDescriptor makeAttributeConverterDescriptor(AttributeConversionInfo conversion) {
|
||||
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) {
|
||||
throw new AnnotationException( "Unable to create AttributeConverter instance", e );
|
||||
|
|
|
@ -12,20 +12,20 @@ import java.lang.reflect.TypeVariable;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Converter;
|
||||
|
||||
import org.hibernate.AnnotationException;
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Externalized representation of an AttributeConverter
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*
|
||||
* @see org.hibernate.boot.spi.AttributeConverterDescriptor
|
||||
*/
|
||||
public class AttributeConverterDefinition {
|
||||
private static final Logger log = Logger.getLogger( AttributeConverterDefinition.class );
|
||||
|
||||
private final AttributeConverter attributeConverter;
|
||||
private final boolean autoApply;
|
||||
private final Class entityAttributeType;
|
||||
|
|
|
@ -25,7 +25,7 @@ import org.hibernate.annotations.ManyToAny;
|
|||
import org.hibernate.annotations.MapKeyType;
|
||||
import org.hibernate.annotations.common.reflection.XClass;
|
||||
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.internal.CoreLogging;
|
||||
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" );
|
||||
if ( info != null ) {
|
||||
if ( info.isConversionDisabled() ) {
|
||||
|
@ -353,7 +353,7 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
|
|||
}
|
||||
else {
|
||||
try {
|
||||
return makeAttributeConverterDefinition( info );
|
||||
return makeAttributeConverterDescriptor( info );
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException(
|
||||
|
@ -369,25 +369,11 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
|
|||
collection.getRole()
|
||||
);
|
||||
|
||||
final Class elementClass = determineElementClass( 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
// todo : do we need to pass along `XClass elementXClass`?
|
||||
|
||||
return null;
|
||||
return getContext().getMetadataCollector()
|
||||
.getAttributeConverterAutoApplyHandler()
|
||||
.findAutoApplyConverterForCollectionElement( collectionXProperty, getContext() );
|
||||
}
|
||||
|
||||
private Class determineElementClass(XClass elementXClass) {
|
||||
|
@ -419,7 +405,7 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
|
|||
return null;
|
||||
}
|
||||
|
||||
public AttributeConverterDefinition keyElementAttributeConverterDefinition(XClass keyXClass) {
|
||||
public AttributeConverterDescriptor mapKeyAttributeConverterDescriptor(XProperty mapXProperty, XClass keyXClass) {
|
||||
AttributeConversionInfo info = locateAttributeConversionInfo( "key" );
|
||||
if ( info != null ) {
|
||||
if ( info.isConversionDisabled() ) {
|
||||
|
@ -427,7 +413,7 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
|
|||
}
|
||||
else {
|
||||
try {
|
||||
return makeAttributeConverterDefinition( info );
|
||||
return makeAttributeConverterDescriptor( info );
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException(
|
||||
|
@ -443,25 +429,11 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
|
|||
collection.getRole()
|
||||
);
|
||||
|
||||
final Class elementClass = determineKeyClass( 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
// todo : do we need to pass along `XClass keyXClass`?
|
||||
|
||||
return null;
|
||||
return getContext().getMetadataCollector()
|
||||
.getAttributeConverterAutoApplyHandler()
|
||||
.findAutoApplyConverterForMapKey( mapXProperty, getContext() );
|
||||
}
|
||||
|
||||
private Class determineKeyClass(XClass keyXClass) {
|
||||
|
|
|
@ -12,6 +12,7 @@ import javax.persistence.JoinTable;
|
|||
|
||||
import org.hibernate.annotations.common.reflection.XClass;
|
||||
import org.hibernate.annotations.common.reflection.XProperty;
|
||||
import org.hibernate.boot.spi.AttributeConverterDescriptor;
|
||||
import org.hibernate.mapping.Join;
|
||||
import org.hibernate.mapping.KeyValue;
|
||||
import org.hibernate.mapping.PersistentClass;
|
||||
|
@ -91,5 +92,5 @@ public interface PropertyHolder {
|
|||
* @param property
|
||||
* @return
|
||||
*/
|
||||
AttributeConverterDefinition resolveAttributeConverterDefinition(XProperty property);
|
||||
AttributeConverterDescriptor resolveAttributeConverterDescriptor(XProperty property);
|
||||
}
|
||||
|
|
|
@ -1460,7 +1460,7 @@ public abstract class CollectionBinder {
|
|||
property,
|
||||
elementClass,
|
||||
collValue.getOwnerEntityName(),
|
||||
holder.resolveElementAttributeConverterDefinition( elementClass )
|
||||
holder.resolveElementAttributeConverterDescriptor( property, elementClass )
|
||||
);
|
||||
elementBinder.setPersistentClassName( propertyHolder.getEntityName() );
|
||||
elementBinder.setAccessType( accessType );
|
||||
|
|
|
@ -275,7 +275,7 @@ public class MapBinder extends CollectionBinder {
|
|||
property,
|
||||
keyXClass,
|
||||
this.collection.getOwnerEntityName(),
|
||||
holder.keyElementAttributeConverterDefinition( keyXClass )
|
||||
holder.mapKeyAttributeConverterDescriptor( property, keyXClass )
|
||||
);
|
||||
elementBinder.setPersistentClassName( propertyHolder.getEntityName() );
|
||||
elementBinder.setAccessType( accessType );
|
||||
|
|
|
@ -180,7 +180,7 @@ public class PropertyBinder {
|
|||
property,
|
||||
returnedClass,
|
||||
containerClassName,
|
||||
holder.resolveAttributeConverterDefinition( property )
|
||||
holder.resolveAttributeConverterDescriptor( property )
|
||||
);
|
||||
simpleValueBinder.setReferencedEntityName( referencedEntityName );
|
||||
simpleValueBinder.setAccessType( accessType );
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.hibernate.annotations.common.reflection.XClass;
|
|||
import org.hibernate.annotations.common.reflection.XProperty;
|
||||
import org.hibernate.annotations.common.util.StandardClassLoaderDelegateImpl;
|
||||
import org.hibernate.boot.model.TypeDefinition;
|
||||
import org.hibernate.boot.spi.AttributeConverterDescriptor;
|
||||
import org.hibernate.boot.spi.MetadataBuildingContext;
|
||||
import org.hibernate.cfg.AccessType;
|
||||
import org.hibernate.cfg.AttributeConverterDefinition;
|
||||
|
@ -86,7 +87,7 @@ public class SimpleValueBinder {
|
|||
private XProperty xproperty;
|
||||
private AccessType accessType;
|
||||
|
||||
private AttributeConverterDefinition attributeConverterDefinition;
|
||||
private AttributeConverterDescriptor attributeConverterDescriptor;
|
||||
|
||||
public void setReferencedEntityName(String referencedEntityName) {
|
||||
this.referencedEntityName = referencedEntityName;
|
||||
|
@ -131,7 +132,7 @@ public class SimpleValueBinder {
|
|||
|
||||
//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 ) {
|
||||
// we cannot guess anything
|
||||
return;
|
||||
|
@ -302,11 +303,11 @@ public class SimpleValueBinder {
|
|||
defaultType = BinderHelper.isEmptyAnnotationValue( type ) ? returnedClassName : type;
|
||||
this.typeParameters = typeParameters;
|
||||
|
||||
applyAttributeConverter( property, attributeConverterDefinition );
|
||||
applyAttributeConverter( property, attributeConverterDescriptor );
|
||||
}
|
||||
|
||||
private void applyAttributeConverter(XProperty property, AttributeConverterDefinition attributeConverterDefinition) {
|
||||
if ( attributeConverterDefinition == null ) {
|
||||
private void applyAttributeConverter(XProperty property, AttributeConverterDescriptor attributeConverterDescriptor) {
|
||||
if ( attributeConverterDescriptor == null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -337,7 +338,7 @@ public class SimpleValueBinder {
|
|||
return;
|
||||
}
|
||||
|
||||
this.attributeConverterDefinition = attributeConverterDefinition;
|
||||
this.attributeConverterDescriptor = attributeConverterDescriptor;
|
||||
}
|
||||
|
||||
private boolean isAssociation() {
|
||||
|
@ -429,7 +430,7 @@ public class SimpleValueBinder {
|
|||
public void fillSimpleValue() {
|
||||
LOG.debugf( "Starting fillSimpleValue for %s", propertyName );
|
||||
|
||||
if ( attributeConverterDefinition != null ) {
|
||||
if ( attributeConverterDescriptor != null ) {
|
||||
if ( ! BinderHelper.isEmptyAnnotationValue( explicitType ) ) {
|
||||
throw new AnnotationException(
|
||||
String.format(
|
||||
|
@ -442,11 +443,11 @@ public class SimpleValueBinder {
|
|||
}
|
||||
LOG.debugf(
|
||||
"Applying JPA AttributeConverter [%s] to [%s:%s]",
|
||||
attributeConverterDefinition,
|
||||
attributeConverterDescriptor,
|
||||
persistentClassName,
|
||||
propertyName
|
||||
);
|
||||
simpleValue.setJpaAttributeConverterDefinition( attributeConverterDefinition );
|
||||
simpleValue.setJpaAttributeConverterDescriptor( attributeConverterDescriptor );
|
||||
}
|
||||
else {
|
||||
String type;
|
||||
|
@ -480,7 +481,7 @@ public class SimpleValueBinder {
|
|||
simpleValue.setTypeName( type );
|
||||
}
|
||||
|
||||
if ( persistentClassName != null || attributeConverterDefinition != null ) {
|
||||
if ( persistentClassName != null || attributeConverterDescriptor != null ) {
|
||||
try {
|
||||
simpleValue.setTypeUsingReflection( persistentClassName, propertyName );
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.io.Serializable;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
@ -803,4 +804,29 @@ public final class StringHelper {
|
|||
public static List<String> parseCommaSeparatedString(String incomingString) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,10 +19,11 @@ import javax.persistence.AttributeConverter;
|
|||
import org.hibernate.FetchMode;
|
||||
import org.hibernate.MappingException;
|
||||
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.ClassLoadingException;
|
||||
import org.hibernate.boot.spi.AttributeConverterDescriptor;
|
||||
import org.hibernate.boot.spi.MetadataImplementor;
|
||||
import org.hibernate.cfg.AttributeConverterDefinition;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.engine.config.spi.ConfigurationService;
|
||||
|
@ -75,7 +76,7 @@ public class SimpleValue implements KeyValue {
|
|||
private boolean alternateUniqueKey;
|
||||
private boolean cascadeDeleteEnabled;
|
||||
|
||||
private AttributeConverterDefinition attributeConverterDefinition;
|
||||
private AttributeConverterDescriptor attributeConverterDescriptor;
|
||||
private Type type;
|
||||
|
||||
public SimpleValue(MetadataImplementor metadata) {
|
||||
|
@ -155,7 +156,7 @@ public class SimpleValue implements KeyValue {
|
|||
.getService( ClassLoaderService.class );
|
||||
try {
|
||||
final Class<AttributeConverter> converterClass = cls.classForName( converterClassName );
|
||||
attributeConverterDefinition = new AttributeConverterDefinition( converterClass.newInstance(), false );
|
||||
attributeConverterDescriptor = new AttributeConverterDescriptorNonAutoApplicableImpl( converterClass.newInstance() );
|
||||
return;
|
||||
}
|
||||
catch (Exception e) {
|
||||
|
@ -420,7 +421,7 @@ public class SimpleValue implements KeyValue {
|
|||
return;
|
||||
}
|
||||
|
||||
if ( attributeConverterDefinition == null ) {
|
||||
if ( attributeConverterDescriptor == null ) {
|
||||
// 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
|
||||
// keep a registry of [SqlTypeDescriptor,JavaTypeDescriptor] -> BasicType...)
|
||||
|
@ -475,8 +476,8 @@ public class SimpleValue implements KeyValue {
|
|||
private Type buildAttributeConverterTypeAdapter() {
|
||||
// todo : validate the number of columns present here?
|
||||
|
||||
final Class entityAttributeJavaType = attributeConverterDefinition.getEntityAttributeType();
|
||||
final Class databaseColumnJavaType = attributeConverterDefinition.getDatabaseColumnType();
|
||||
final Class entityAttributeJavaType = attributeConverterDescriptor.getDomainType();
|
||||
final Class databaseColumnJavaType = attributeConverterDescriptor.getJdbcType();
|
||||
|
||||
|
||||
// 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
|
||||
// process...
|
||||
final SqlTypeDescriptor sqlTypeDescriptorAdapter = new AttributeConverterSqlTypeDescriptorAdapter(
|
||||
attributeConverterDefinition.getAttributeConverter(),
|
||||
attributeConverterDescriptor.getAttributeConverter(),
|
||||
sqlTypeDescriptor,
|
||||
intermediateJavaTypeDescriptor
|
||||
);
|
||||
|
||||
// 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(
|
||||
"BasicType adapter for AttributeConverter<%s,%s>",
|
||||
entityAttributeJavaType.getSimpleName(),
|
||||
|
@ -539,7 +540,7 @@ public class SimpleValue implements KeyValue {
|
|||
return new AttributeConverterTypeAdapter(
|
||||
name,
|
||||
description,
|
||||
attributeConverterDefinition.getAttributeConverter(),
|
||||
attributeConverterDescriptor.getAttributeConverter(),
|
||||
sqlTypeDescriptorAdapter,
|
||||
entityAttributeJavaType,
|
||||
databaseColumnJavaType,
|
||||
|
@ -583,8 +584,8 @@ public class SimpleValue implements KeyValue {
|
|||
return getColumnInsertability();
|
||||
}
|
||||
|
||||
public void setJpaAttributeConverterDefinition(AttributeConverterDefinition attributeConverterDefinition) {
|
||||
this.attributeConverterDefinition = attributeConverterDefinition;
|
||||
public void setJpaAttributeConverterDescriptor(AttributeConverterDescriptor attributeConverterDescriptor) {
|
||||
this.attributeConverterDescriptor = attributeConverterDescriptor;
|
||||
}
|
||||
|
||||
private void createParameterImpl() {
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.hibernate.IrrelevantEntity;
|
|||
import org.hibernate.Session;
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.boot.MetadataSources;
|
||||
import org.hibernate.boot.internal.AttributeConverterDescriptorNonAutoApplicableImpl;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistry;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||
import org.hibernate.boot.spi.MetadataImplementor;
|
||||
|
@ -96,8 +97,8 @@ public class AttributeConverterTest extends BaseUnitTestCase {
|
|||
try {
|
||||
MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr ).buildMetadata();
|
||||
SimpleValue simpleValue = new SimpleValue( metadata );
|
||||
simpleValue.setJpaAttributeConverterDefinition(
|
||||
new AttributeConverterDefinition( new StringClobConverter(), true )
|
||||
simpleValue.setJpaAttributeConverterDescriptor(
|
||||
new AttributeConverterDescriptorNonAutoApplicableImpl( new StringClobConverter() )
|
||||
);
|
||||
simpleValue.setTypeUsingReflection( IrrelevantEntity.class.getName(), "name" );
|
||||
|
||||
|
|
|
@ -8,14 +8,29 @@ package org.hibernate.test.converter.generics;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
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.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.junit4.BaseUnitTestCase;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
|
@ -27,6 +42,20 @@ import static org.junit.Assert.assertEquals;
|
|||
*/
|
||||
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> {
|
||||
@Override
|
||||
public Integer convertToDatabaseColumn(List<String> attribute) {
|
||||
|
@ -46,4 +75,119 @@ public class ParameterizedAttributeConverterParameterTypeTest extends BaseUnitTe
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ ext {
|
|||
// Annotations
|
||||
commons_annotations: 'org.hibernate.common:hibernate-commons-annotations:5.0.0.Final',
|
||||
jandex: 'org.jboss:jandex:2.0.0.CR1',
|
||||
classmate: 'com.fasterxml:classmate:0.8.0',
|
||||
classmate: 'com.fasterxml:classmate:1.3.0',
|
||||
|
||||
// Woodstox
|
||||
woodstox: "org.codehaus.woodstox:woodstox-core-asl:4.3.0",
|
||||
|
|
Loading…
Reference in New Issue