diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AbstractPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AbstractPropertyHolder.java index bccd2d9a36..22d8e47c53 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AbstractPropertyHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AbstractPropertyHolder.java @@ -22,8 +22,7 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.cfg; -import java.util.HashMap; -import java.util.Map; + import javax.persistence.AssociationOverride; import javax.persistence.AssociationOverrides; import javax.persistence.AttributeOverride; @@ -34,11 +33,17 @@ import javax.persistence.Entity; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.MappedSuperclass; +import java.util.HashMap; +import java.util.Map; +import org.jboss.logging.Logger; + +import org.hibernate.AnnotationException; 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.internal.CoreLogging; import org.hibernate.internal.util.StringHelper; /** @@ -47,6 +52,8 @@ import org.hibernate.internal.util.StringHelper; * @author Emmanuel Bernard */ public abstract class AbstractPropertyHolder implements PropertyHolder { + private static final Logger log = CoreLogging.logger( AbstractPropertyHolder.class ); + protected AbstractPropertyHolder parent; private Map holderColumnOverride; private Map currentPropertyColumnOverride; @@ -58,7 +65,6 @@ public abstract class AbstractPropertyHolder implements PropertyHolder { private Mappings mappings; private Boolean isInIdClass; - AbstractPropertyHolder( String path, PropertyHolder parent, @@ -70,6 +76,122 @@ public abstract class AbstractPropertyHolder implements PropertyHolder { buildHierarchyColumnOverride( clazzToProcess ); } + protected abstract String normalizeCompositePathForLogging(String attributeName); + protected abstract String normalizeCompositePath(String attributeName); + + protected abstract AttributeConversionInfo locateAttributeConversionInfo(XProperty property); + protected abstract AttributeConversionInfo locateAttributeConversionInfo(String path); + + @Override + public AttributeConverterDefinition resolveAttributeConverterDefinition(XProperty property) { + AttributeConversionInfo info = locateAttributeConversionInfo( property ); + if ( info != null ) { + if ( info.isConversionDisabled() ) { + return null; + } + else { + try { + return makeAttributeConverterDefinition( info ); + } + catch (Exception e) { + throw new IllegalStateException( + String.format( "Unable to instantiate AttributeConverter [%s", info.getConverterClass().getName() ), + e + ); + } + } + } + + // look for auto applied converters + // todo : hook in the orm.xml local entity work brett did + // for now, just look in global converters + + + log.debugf( "Attempting to locate auto-apply AttributeConverter for property [%s:%s]", path, property.getName() ); + + final Class propertyType = mappings.getReflectionManager().toClass( property.getType() ); + for ( AttributeConverterDefinition attributeConverterDefinition : mappings.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; + } + + private AttributeConverterDefinition makeAttributeConverterDefinition(AttributeConversionInfo conversion) { + try { + return new AttributeConverterDefinition( conversion.getConverterClass().newInstance(), false ); + } + catch (Exception e) { + throw new AnnotationException( "Unable to create AttributeConverter instance", e ); + } + } + + private boolean areTypeMatch(Class converterDefinedType, Class propertyType) { + if ( converterDefinedType == null ) { + throw new AnnotationException( "AttributeConverter defined java type cannot be null" ); + } + if ( propertyType == null ) { + throw new AnnotationException( "Property defined java type cannot be null" ); + } + + return converterDefinedType.equals( propertyType ) + || arePrimitiveWrapperEquivalents( converterDefinedType, propertyType ); + } + + private boolean arePrimitiveWrapperEquivalents(Class converterDefinedType, Class propertyType) { + if ( converterDefinedType.isPrimitive() ) { + return getWrapperEquivalent( converterDefinedType ).equals( propertyType ); + } + else if ( propertyType.isPrimitive() ) { + return getWrapperEquivalent( propertyType ).equals( converterDefinedType ); + } + return false; + } + + private static Class getWrapperEquivalent(Class primitive) { + if ( ! primitive.isPrimitive() ) { + throw new AssertionFailure( "Passed type for which to locate wrapper equivalent was not a primitive" ); + } + + if ( boolean.class.equals( primitive ) ) { + return Boolean.class; + } + else if ( char.class.equals( primitive ) ) { + return Character.class; + } + else if ( byte.class.equals( primitive ) ) { + return Byte.class; + } + else if ( short.class.equals( primitive ) ) { + return Short.class; + } + else if ( int.class.equals( primitive ) ) { + return Integer.class; + } + else if ( long.class.equals( primitive ) ) { + return Long.class; + } + else if ( float.class.equals( primitive ) ) { + return Float.class; + } + else if ( double.class.equals( primitive ) ) { + return Double.class; + } + + throw new AssertionFailure( "Unexpected primitive type (VOID most likely) passed to getWrapperEquivalent" ); + } + @Override public boolean isInIdClass() { return isInIdClass != null ? isInIdClass : parent != null ? parent.isInIdClass() : false; diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index 3d982551b6..8ef9b03f86 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -135,6 +135,8 @@ import org.hibernate.annotations.Tuplizers; import org.hibernate.annotations.TypeDef; import org.hibernate.annotations.TypeDefs; import org.hibernate.annotations.Where; +import org.hibernate.annotations.common.reflection.ClassLoaderDelegate; +import org.hibernate.annotations.common.reflection.ClassLoadingException; import org.hibernate.annotations.common.reflection.ReflectionManager; import org.hibernate.annotations.common.reflection.XAnnotatedElement; import org.hibernate.annotations.common.reflection.XClass; @@ -292,6 +294,10 @@ public final class AnnotationBinder { try { pckg = mappings.getReflectionManager().packageForName( packageName ); } + catch (ClassLoadingException e) { + LOG.packageNotFound( packageName ); + return; + } catch ( ClassNotFoundException cnf ) { LOG.packageNotFound( packageName ); return; @@ -2415,10 +2421,18 @@ public final class AnnotationBinder { String subpath = BinderHelper.getPath( propertyHolder, inferredData ); LOG.tracev( "Binding component with path: {0}", subpath ); PropertyHolder subHolder = PropertyHolderBuilder.buildPropertyHolder( - comp, subpath, - inferredData, propertyHolder, mappings + comp, + subpath, + inferredData, + propertyHolder, + mappings ); + + // propertyHolder here is the owner of the component property. Tell it we are about to start the component... + + propertyHolder.startingProperty( inferredData.getProperty() ); + final XClass xClassProcessed = inferredData.getPropertyClass(); List classElements = new ArrayList(); XClass returnedClassOrElement = inferredData.getClassOrElement(); @@ -2599,7 +2613,7 @@ public final class AnnotationBinder { value.setColumns( columns ); value.setPersistentClassName( persistentClassName ); value.setMappings( mappings ); - value.setType( inferredData.getProperty(), inferredData.getClassOrElement(), persistentClassName ); + value.setType( inferredData.getProperty(), inferredData.getClassOrElement(), persistentClassName, null ); value.setAccessType( propertyAccessor ); id = value.make(); } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AttributeConversionInfo.java b/hibernate-core/src/main/java/org/hibernate/cfg/AttributeConversionInfo.java new file mode 100644 index 0000000000..ece8b90faf --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AttributeConversionInfo.java @@ -0,0 +1,80 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.cfg; + +import javax.persistence.AttributeConverter; +import javax.persistence.Convert; + +import org.hibernate.annotations.common.reflection.XAnnotatedElement; + +/** + * Describes a {@link javax.persistence.Convert} conversion + * + * @author Steve Ebersole + */ +public class AttributeConversionInfo { + private final Class converterClass; + private final boolean conversionDisabled; + + private final String attributeName; + + private final XAnnotatedElement source; + + public AttributeConversionInfo( + Class converterClass, + boolean conversionDisabled, + String attributeName, + XAnnotatedElement source) { + this.converterClass = converterClass; + this.conversionDisabled = conversionDisabled; + this.attributeName = attributeName; + this.source = source; + } + + @SuppressWarnings("unchecked") + public AttributeConversionInfo(Convert convertAnnotation, XAnnotatedElement xAnnotatedElement) { + this( + convertAnnotation.converter(), + convertAnnotation.disableConversion(), + convertAnnotation.attributeName(), + xAnnotatedElement + ); + } + + public Class getConverterClass() { + return converterClass; + } + + public boolean isConversionDisabled() { + return conversionDisabled; + } + + public String getAttributeName() { + return attributeName; + } + + public XAnnotatedElement getSource() { + return source; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/ClassPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/cfg/ClassPropertyHolder.java index 925bfe60e2..b83b49c9a1 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/ClassPropertyHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/ClassPropertyHolder.java @@ -22,21 +22,21 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.cfg; -import java.util.HashMap; -import java.util.Map; + import javax.persistence.Convert; import javax.persistence.Converts; import javax.persistence.JoinTable; +import java.util.HashMap; +import java.util.Map; import org.jboss.logging.Logger; -import org.hibernate.AnnotationException; import org.hibernate.annotations.common.AssertionFailure; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; -import org.hibernate.annotations.common.util.ReflectHelper; import org.hibernate.cfg.annotations.EntityBinder; import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.util.StringHelper; import org.hibernate.mapping.Component; import org.hibernate.mapping.Join; import org.hibernate.mapping.KeyValue; @@ -49,36 +49,145 @@ import org.hibernate.mapping.Table; * @author Emmanuel Bernard */ public class ClassPropertyHolder extends AbstractPropertyHolder { - private static final Logger log = CoreLogging.logger( ClassPropertyHolder.class ); - private PersistentClass persistentClass; private Map joins; private transient Map joinsPerRealTableName; private EntityBinder entityBinder; private final Map inheritanceStatePerClass; + private Map attributeConversionInfoMap; + public ClassPropertyHolder( PersistentClass persistentClass, - XClass clazzToProcess, + XClass entityXClass, Map joins, Mappings mappings, Map inheritanceStatePerClass) { - super( persistentClass.getEntityName(), null, clazzToProcess, mappings ); + super( persistentClass.getEntityName(), null, entityXClass, mappings ); this.persistentClass = persistentClass; this.joins = joins; this.inheritanceStatePerClass = inheritanceStatePerClass; + + this.attributeConversionInfoMap = buildAttributeConversionInfoMap( entityXClass ); } public ClassPropertyHolder( PersistentClass persistentClass, - XClass clazzToProcess, + XClass entityXClass, EntityBinder entityBinder, Mappings mappings, Map inheritanceStatePerClass) { - this( persistentClass, clazzToProcess, entityBinder.getSecondaryTables(), mappings, inheritanceStatePerClass ); + this( persistentClass, entityXClass, entityBinder.getSecondaryTables(), mappings, inheritanceStatePerClass ); this.entityBinder = entityBinder; } + @Override + protected String normalizeCompositePath(String attributeName) { + return attributeName; + } + + @Override + protected String normalizeCompositePathForLogging(String attributeName) { + return getEntityName() + '.' + attributeName; + } + + protected Map buildAttributeConversionInfoMap(XClass entityXClass) { + final HashMap map = new HashMap(); + collectAttributeConversionInfo( map, entityXClass ); + return map; + } + + private void collectAttributeConversionInfo(Map infoMap, XClass xClass) { + if ( xClass == null ) { + // typically indicates we have reached the end of the inheritance hierarchy + return; + } + + // collect superclass info first + collectAttributeConversionInfo( infoMap, xClass.getSuperclass() ); + + final boolean canContainConvert = xClass.isAnnotationPresent( javax.persistence.Entity.class ) + || xClass.isAnnotationPresent( javax.persistence.MappedSuperclass.class ) + || xClass.isAnnotationPresent( javax.persistence.Embeddable.class ); + if ( ! canContainConvert ) { + return; + } + + { + final Convert convertAnnotation = xClass.getAnnotation( Convert.class ); + if ( convertAnnotation != null ) { + final AttributeConversionInfo info = new AttributeConversionInfo( convertAnnotation, xClass ); + if ( StringHelper.isEmpty( info.getAttributeName() ) ) { + throw new IllegalStateException( "@Convert placed on @Entity/@MappedSuperclass must define attributeName" ); + } + infoMap.put( info.getAttributeName(), info ); + } + } + { + final Converts convertsAnnotation = xClass.getAnnotation( Converts.class ); + if ( convertsAnnotation != null ) { + for ( Convert convertAnnotation : convertsAnnotation.value() ) { + final AttributeConversionInfo info = new AttributeConversionInfo( convertAnnotation, xClass ); + if ( StringHelper.isEmpty( info.getAttributeName() ) ) { + throw new IllegalStateException( "@Converts placed on @Entity/@MappedSuperclass must define attributeName" ); + } + infoMap.put( info.getAttributeName(), info ); + } + } + } + } + + @Override + public void startingProperty(XProperty property) { + if ( property == null ) { + return; + } + + final String propertyName = property.getName(); + if ( attributeConversionInfoMap.containsKey( propertyName ) ) { + return; + } + + { + // @Convert annotation on the Embeddable attribute + final Convert convertAnnotation = property.getAnnotation( Convert.class ); + if ( convertAnnotation != null ) { + final AttributeConversionInfo info = new AttributeConversionInfo( convertAnnotation, property ); + if ( StringHelper.isEmpty( info.getAttributeName() ) ) { + attributeConversionInfoMap.put( propertyName, info ); + } + else { + attributeConversionInfoMap.put( propertyName + '.' + info.getAttributeName(), info ); + } + } + } + { + // @Converts annotation on the Embeddable attribute + final Converts convertsAnnotation = property.getAnnotation( Converts.class ); + if ( convertsAnnotation != null ) { + for ( Convert convertAnnotation : convertsAnnotation.value() ) { + final AttributeConversionInfo info = new AttributeConversionInfo( convertAnnotation, property ); + if ( StringHelper.isEmpty( info.getAttributeName() ) ) { + attributeConversionInfoMap.put( propertyName, info ); + } + else { + attributeConversionInfoMap.put( propertyName + '.' + info.getAttributeName(), info ); + } + } + } + } + } + + @Override + protected AttributeConversionInfo locateAttributeConversionInfo(XProperty property) { + return locateAttributeConversionInfo( property.getName() ); + } + + @Override + protected AttributeConversionInfo locateAttributeConversionInfo(String path) { + return attributeConversionInfoMap.get( path ); + } + public String getEntityName() { return persistentClass.getEntityName(); } @@ -286,4 +395,10 @@ public class ClassPropertyHolder extends AbstractPropertyHolder { // // return lookForEntityDefinedConvertAnnotation( property, owner ); // } + + + @Override + public String toString() { + return super.toString() + "(" + getEntityName() + ")"; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/CollectionPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/cfg/CollectionPropertyHolder.java index 84656bd428..3db168c2f5 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/CollectionPropertyHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/CollectionPropertyHolder.java @@ -22,6 +22,7 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.cfg; + import javax.persistence.JoinTable; import org.hibernate.AssertionFailure; @@ -52,6 +53,33 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder { setCurrentProperty( property ); } + @Override + protected String normalizeCompositePath(String attributeName) { + return attributeName; + } + + @Override + protected String normalizeCompositePathForLogging(String attributeName) { + return collection.getRole() + '.' + attributeName; + } + + @Override + public void startingProperty(XProperty property) { + // todo : implement + } + + @Override + protected AttributeConversionInfo locateAttributeConversionInfo(XProperty property) { + // todo : implement + return null; + } + + @Override + protected AttributeConversionInfo locateAttributeConversionInfo(String path) { + // todo : implement + return null; + } + public String getClassName() { throw new AssertionFailure( "Collection property holder does not have a class name" ); } @@ -100,4 +128,9 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder { public Join addJoin(JoinTable joinTableAnn, boolean noDelayInPkColumnCreation) { throw new AssertionFailure( "Add a in a second pass" ); } + + @Override + public String toString() { + return super.toString() + "(" + collection.getRole() + ")"; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/ComponentPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/cfg/ComponentPropertyHolder.java index 2bf5f45fda..7fc1137540 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/ComponentPropertyHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/ComponentPropertyHolder.java @@ -23,14 +23,21 @@ */ package org.hibernate.cfg; import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.Converts; import javax.persistence.EmbeddedId; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + import org.hibernate.AnnotationException; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; +import org.hibernate.internal.util.StringHelper; import org.hibernate.mapping.Component; import org.hibernate.mapping.Join; import org.hibernate.mapping.KeyValue; @@ -39,8 +46,31 @@ import org.hibernate.mapping.Property; import org.hibernate.mapping.Table; /** - * Component implementation of property holder + * PropertyHolder for composites (Embeddable/Embedded). + *

+ * To facilitate code comments, I'll often refer to this example: + *

+ *     @Embeddable
+ *     @Convert( attributeName="city", ... )
+ *     class Address {
+ *         ...
+ *         @Convert(...)
+ *         public String city;
+ *     }
  *
+ *     @Entity
+ *     @Convert( attributeName="homeAddress.city", ... )
+ *     class Person {
+ *         ...
+ *         @Embedded
+ *         @Convert( attributeName="city", ... )
+ *         public Address homeAddress;
+ *     }
+ * 
+ * + * As you can see, lots of ways to specify the conversion for embeddable attributes :( + * + * @author Steve Ebersole * @author Emmanuel Bernard */ public class ComponentPropertyHolder extends AbstractPropertyHolder { @@ -49,6 +79,201 @@ public class ComponentPropertyHolder extends AbstractPropertyHolder { private Component component; private boolean isOrWithinEmbeddedId; + private boolean virtual; + private String embeddedAttributeName; + private Map attributeConversionInfoMap; + + public ComponentPropertyHolder( + Component component, + String path, + PropertyData inferredData, + PropertyHolder parent, + Mappings mappings) { + super( path, parent, inferredData.getPropertyClass(), mappings ); + final XProperty embeddedXProperty = inferredData.getProperty(); + setCurrentProperty( embeddedXProperty ); + this.component = component; + this.isOrWithinEmbeddedId = + parent.isOrWithinEmbeddedId() + || ( embeddedXProperty != null && + ( embeddedXProperty.isAnnotationPresent( Id.class ) + || embeddedXProperty.isAnnotationPresent( EmbeddedId.class ) ) ); + + this.virtual = embeddedXProperty == null; + if ( !virtual ) { + this.embeddedAttributeName = embeddedXProperty.getName(); + this.attributeConversionInfoMap = processAttributeConversions( embeddedXProperty ); + } + else { + embeddedAttributeName = ""; + this.attributeConversionInfoMap = Collections.emptyMap(); + } + } + + /** + * This is called from our constructor and handles (in order):
    + *
  1. @Convert annotation at the Embeddable class level
  2. + *
  3. @Converts annotation at the Embeddable class level
  4. + *
  5. @Convert annotation at the Embedded attribute level
  6. + *
  7. @Converts annotation at the Embedded attribute level
  8. + *
+ *

+ * The order is important to ensure proper precedence. + *

+ * {@literal @Convert/@Converts} annotations at the Embeddable attribute level are handled in the calls to + * {@link #startingProperty}. Duplicates are simply ignored there. + * + * @param embeddedXProperty The property that is the composite being described by this ComponentPropertyHolder + */ + private Map processAttributeConversions(XProperty embeddedXProperty) { + final Map infoMap = new HashMap(); + + final XClass embeddableXClass = embeddedXProperty.getType(); + + // as a baseline, we want to apply conversions from the Embeddable and then overlay conversions + // from the Embedded + + // first apply conversions from the Embeddable... + { + // @Convert annotation on the Embeddable class level + final Convert convertAnnotation = embeddableXClass.getAnnotation( Convert.class ); + if ( convertAnnotation != null ) { + final AttributeConversionInfo info = new AttributeConversionInfo( convertAnnotation, embeddableXClass ); + if ( StringHelper.isEmpty( info.getAttributeName() ) ) { + throw new IllegalStateException( "@Convert placed on @Embeddable must define attributeName" ); + } + infoMap.put( info.getAttributeName(), info ); + } + } + { + // @Converts annotation on the Embeddable class level + final Converts convertsAnnotation = embeddableXClass.getAnnotation( Converts.class ); + if ( convertsAnnotation != null ) { + for ( Convert convertAnnotation : convertsAnnotation.value() ) { + final AttributeConversionInfo info = new AttributeConversionInfo( convertAnnotation, embeddableXClass ); + if ( StringHelper.isEmpty( info.getAttributeName() ) ) { + throw new IllegalStateException( "@Converts placed on @Embeddable must define attributeName" ); + } + infoMap.put( info.getAttributeName(), info ); + } + } + } + + // then we can overlay any conversions from the Embedded attribute + { + // @Convert annotation on the Embedded attribute + final Convert convertAnnotation = embeddedXProperty.getAnnotation( Convert.class ); + if ( convertAnnotation != null ) { + final AttributeConversionInfo info = new AttributeConversionInfo( convertAnnotation, embeddableXClass ); + if ( StringHelper.isEmpty( info.getAttributeName() ) ) { + throw new IllegalStateException( "Convert placed on Embedded attribute must define (sub)attributeName" ); + } + infoMap.put( info.getAttributeName(), info ); + } + } + { + // @Converts annotation on the Embedded attribute + final Converts convertsAnnotation = embeddedXProperty.getAnnotation( Converts.class ); + if ( convertsAnnotation != null ) { + for ( Convert convertAnnotation : convertsAnnotation.value() ) { + final AttributeConversionInfo info = new AttributeConversionInfo( convertAnnotation, embeddableXClass ); + if ( StringHelper.isEmpty( info.getAttributeName() ) ) { + throw new IllegalStateException( "Convert placed on Embedded attribute must define (sub)attributeName" ); + } + infoMap.put( info.getAttributeName(), info ); + } + } + } + + return infoMap; + } + + @Override + protected String normalizeCompositePath(String attributeName) { + return embeddedAttributeName + '.' + attributeName; + } + + @Override + protected String normalizeCompositePathForLogging(String attributeName) { + return normalizeCompositePath( attributeName ); + } + + @Override + public void startingProperty(XProperty property) { + if ( property == null ) { + return; + } + + if ( virtual ) { + return; + } + + // again : the property coming in here *should* be the property on the embeddable (Address#city in the example), + // so we just ignore it if there is already an existing conversion info for that path since they would have + // precedence + + // technically we should only do this for properties of "basic type" + + final String path = embeddedAttributeName + '.' + property.getName(); + if ( attributeConversionInfoMap.containsKey( path ) ) { + return; + } + + { + // @Convert annotation on the Embeddable attribute + final Convert convertAnnotation = property.getAnnotation( Convert.class ); + if ( convertAnnotation != null ) { + final AttributeConversionInfo info = new AttributeConversionInfo( convertAnnotation, property ); + attributeConversionInfoMap.put( property.getName(), info ); + } + } + { + // @Converts annotation on the Embeddable attribute + final Converts convertsAnnotation = property.getAnnotation( Converts.class ); + if ( convertsAnnotation != null ) { + for ( Convert convertAnnotation : convertsAnnotation.value() ) { + final AttributeConversionInfo info = new AttributeConversionInfo( convertAnnotation, property ); + attributeConversionInfoMap.put( property.getName(), info ); + } + } + } + } + + @Override + protected AttributeConversionInfo locateAttributeConversionInfo(XProperty property) { + final String propertyName = property.getName(); + + // conversions on parent would have precedence + AttributeConversionInfo conversion = locateAttributeConversionInfo( propertyName ); + if ( conversion != null ) { + return conversion; + } + + + Convert localConvert = property.getAnnotation( Convert.class ); + if ( localConvert != null ) { + return new AttributeConversionInfo( localConvert, property ); + } + + return null; + } + + @Override + protected AttributeConversionInfo locateAttributeConversionInfo(String path) { + final String embeddedPath = embeddedAttributeName + '.' + path; + AttributeConversionInfo fromParent = parent.locateAttributeConversionInfo( embeddedPath ); + if ( fromParent != null ) { + return fromParent; + } + + AttributeConversionInfo fromEmbedded = attributeConversionInfoMap.get( embeddedPath ); + if ( fromEmbedded != null ) { + return fromEmbedded; + } + + return attributeConversionInfoMap.get( path ); + } + public String getEntityName() { return component.getComponentClassName(); } @@ -82,23 +307,6 @@ public class ComponentPropertyHolder extends AbstractPropertyHolder { } - public ComponentPropertyHolder( - Component component, - String path, - PropertyData inferredData, - PropertyHolder parent, - Mappings mappings) { - super( path, parent, inferredData.getPropertyClass(), mappings ); - final XProperty property = inferredData.getProperty(); - setCurrentProperty( property ); - this.component = component; - this.isOrWithinEmbeddedId = - parent.isOrWithinEmbeddedId() - || ( property != null && - ( property.isAnnotationPresent( Id.class ) - || property.isAnnotationPresent( EmbeddedId.class ) ) ); - } - public String getClassName() { return component.getComponentClassName(); } @@ -173,4 +381,9 @@ public class ComponentPropertyHolder extends AbstractPropertyHolder { public JoinColumn[] getOverriddenJoinColumn(String propertyName) { return super.getOverriddenJoinColumn( propertyName ); } + + @Override + public String toString() { + return super.toString() + "(" + parent.normalizeCompositePathForLogging( embeddedAttributeName ) + ")"; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/PropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/cfg/PropertyHolder.java index bb79c2810e..1094771b19 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/PropertyHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/PropertyHolder.java @@ -22,6 +22,7 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.cfg; +import javax.persistence.AttributeConverter; import javax.persistence.Column; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; @@ -67,8 +68,6 @@ public interface PropertyHolder { String getPath(); -// public AttributeConverterDefinition resolveAttributeConverter(String attributeName); - /** * return null if the column is not overridden, or an array of column if true */ @@ -94,4 +93,20 @@ public interface PropertyHolder { boolean isInIdClass(); void setInIdClass(Boolean isInIdClass); + + /** + * Called during binding to allow the PropertyHolder to inspect its discovered properties. Mainly + * this is used in collecting attribute conversion declarations (via @Convert/@Converts). + * + * @param property The property + */ + void startingProperty(XProperty property); + + /** + * Determine the AttributeConverter to use for the given property. + * + * @param property + * @return + */ + AttributeConverterDefinition resolveAttributeConverterDefinition(XProperty property); } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/AttributeConverterResolver.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/AttributeConverterResolver.java new file mode 100644 index 0000000000..1fec9188f3 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/AttributeConverterResolver.java @@ -0,0 +1,40 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.cfg.annotations; + +import javax.persistence.AttributeConverter; + +/** + * @author Steve Ebersole + */ +public interface AttributeConverterResolver { + /** + * Resolve the AttributeConverter to use for an attribute during binding. The assumption of the API + * is that the path information is encoded into the AttributeConverterResolver instance itself to account + * for all the different paths converters can be defined on. + * + * @return The AttributeConverter to use, or {@code null} if none. + */ + public AttributeConverter resolveAttributeConverter(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/AttributeConverterResolverContext.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/AttributeConverterResolverContext.java new file mode 100644 index 0000000000..061951ad02 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/AttributeConverterResolverContext.java @@ -0,0 +1,184 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.cfg.annotations; + +import java.util.HashMap; +import java.util.Map; + +import org.hibernate.annotations.common.reflection.XClass; +import org.hibernate.annotations.common.reflection.XProperty; +import org.hibernate.cfg.Mappings; + +/** + * Manages the resolution of the AttributeConverter, if one, for a property (path). + * + * @author Steve Ebersole + */ +public class AttributeConverterResolverContext { + private final Mappings mappings; + + public AttributeConverterResolverContext(Mappings mappings) { + this.mappings = mappings; + } + + private Map entityPropertyPathSourceMap; + + public EntityPropertyPathSource resolveEntityPropertyPathSource(XClass entityClass) { + EntityPropertyPathSource found = null; + if ( entityPropertyPathSourceMap == null ) { + entityPropertyPathSourceMap = new HashMap(); + } + else { + found = entityPropertyPathSourceMap.get( entityClass.getName() ); + } + + if ( found == null ) { + found = new EntityPropertyPathSource( entityClass ); + entityPropertyPathSourceMap.put( entityClass.getName(), found ); + } + + return found; + } + + private Map collectionPropertyPathSourceMap; + + public CollectionPropertyPathSource resolveRootCollectionPropertyPathSource(XClass owner, XProperty property) { + CollectionPropertyPathSource found = null; + if ( collectionPropertyPathSourceMap == null ) { + collectionPropertyPathSourceMap = new HashMap(); + } + else { + found = collectionPropertyPathSourceMap.get( owner.getName() ); + } + + if ( found == null ) { + final EntityPropertyPathSource ownerSource = resolveEntityPropertyPathSource( owner ); + found = new CollectionPropertyPathSource( ownerSource, property.getName() ); + collectionPropertyPathSourceMap.put( owner.getName(), found ); + } + + return found; + } + + public static interface PropertyPathSource { + public CompositePropertyPathSource makeComposite(String propertyName); + public CollectionPropertyPathSource makeCollection(String propertyName); + } + + public static abstract class AbstractPropertyPathSource implements PropertyPathSource { + protected abstract String normalize(String path); + + private Map compositePropertyPathSourceMap; + + @Override + public CompositePropertyPathSource makeComposite(String propertyName) { + CompositePropertyPathSource found = null; + if ( compositePropertyPathSourceMap == null ) { + compositePropertyPathSourceMap = new HashMap(); + } + else { + found = compositePropertyPathSourceMap.get( propertyName ); + } + + if ( found == null ) { + found = new CompositePropertyPathSource( this, propertyName ); + compositePropertyPathSourceMap.put( propertyName, found ); + } + + return found; + } + + private Map collectionPropertyPathSourceMap; + + @Override + public CollectionPropertyPathSource makeCollection(String propertyName) { + CollectionPropertyPathSource found = null; + if ( collectionPropertyPathSourceMap == null ) { + collectionPropertyPathSourceMap = new HashMap(); + } + else { + found = collectionPropertyPathSourceMap.get( propertyName ); + } + + if ( found == null ) { + found = new CollectionPropertyPathSource( this, propertyName ); + collectionPropertyPathSourceMap.put( propertyName, found ); + } + + return found; + } + } + + public static class EntityPropertyPathSource extends AbstractPropertyPathSource implements PropertyPathSource { + private final XClass entityClass; + + private EntityPropertyPathSource(XClass entityClass) { + this.entityClass = entityClass; + } + + @Override + protected String normalize(String path) { + return path; + } + } + + public static class CompositePropertyPathSource extends AbstractPropertyPathSource implements PropertyPathSource { + private final AbstractPropertyPathSource sourceOfComposite; + private final String compositeName; + + public CompositePropertyPathSource(AbstractPropertyPathSource sourceOfComposite, String compositeName) { + this.sourceOfComposite = sourceOfComposite; + this.compositeName = compositeName; + } + + @Override + protected String normalize(String path) { + return getSourceOfComposite().normalize( compositeName ) + "." + path; + } + + public AbstractPropertyPathSource getSourceOfComposite() { + return sourceOfComposite; + } + + public String getCompositeName() { + return compositeName; + } + } + + public static class CollectionPropertyPathSource extends AbstractPropertyPathSource implements PropertyPathSource { + private final AbstractPropertyPathSource collectionSource; + private final String collectionName; + + private CollectionPropertyPathSource(AbstractPropertyPathSource collectionSource, String collectionName) { + this.collectionSource = collectionSource; + this.collectionName = collectionName; + } + + @Override + protected String normalize(String path) { + return path; + } + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java index 5c6ddefbca..539b7c5419 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java @@ -1367,7 +1367,7 @@ public abstract class CollectionBinder { column.setTable( collValue.getCollectionTable() ); } elementBinder.setColumns( elementColumns ); - elementBinder.setType( property, elementClass, collValue.getOwnerEntityName() ); + elementBinder.setType( property, elementClass, collValue.getOwnerEntityName(), null ); elementBinder.setPersistentClassName( propertyHolder.getEntityName() ); elementBinder.setAccessType( accessType ); collValue.setElement( elementBinder.make() ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java index 1e5e7d8150..a137d1daa2 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java @@ -278,7 +278,7 @@ public class MapBinder extends CollectionBinder { elementBinder.setExplicitType( mapKeyTypeAnnotation.value() ); } else { - elementBinder.setType( property, elementClass, this.collection.getOwnerEntityName() ); + elementBinder.setType( property, elementClass, this.collection.getOwnerEntityName(), null ); } elementBinder.setPersistentClassName( propertyHolder.getEntityName() ); elementBinder.setAccessType( accessType ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java index 0aa513d6b3..0fcf74f9d2 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java @@ -176,17 +176,23 @@ public class PropertyBinder { private Property makePropertyAndValue() { validateBind(); + LOG.debugf( "MetadataSourceProcessor property %s with lazy=%s", name, lazy ); - String containerClassName = holder == null ? - null : - holder.getClassName(); + final String containerClassName = holder.getClassName(); + holder.startingProperty( property ); + simpleValueBinder = new SimpleValueBinder(); simpleValueBinder.setMappings( mappings ); simpleValueBinder.setPropertyName( name ); simpleValueBinder.setReturnedClassName( returnedClassName ); simpleValueBinder.setColumns( columns ); simpleValueBinder.setPersistentClassName( containerClassName ); - simpleValueBinder.setType( property, returnedClass, containerClassName ); + simpleValueBinder.setType( + property, + returnedClass, + containerClassName, + holder.resolveAttributeConverterDefinition( property ) + ); simpleValueBinder.setMappings( mappings ); simpleValueBinder.setReferencedEntityName( referencedEntityName ); simpleValueBinder.setAccessType( accessType ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java index 8a78261d37..04d088436f 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java @@ -23,15 +23,6 @@ */ package org.hibernate.cfg.annotations; -import java.io.Serializable; -import java.lang.reflect.TypeVariable; -import java.util.Calendar; -import java.util.Date; -import java.util.Properties; - -import javax.persistence.AttributeConverter; -import javax.persistence.Convert; -import javax.persistence.Converts; import javax.persistence.Enumerated; import javax.persistence.Id; import javax.persistence.Lob; @@ -39,6 +30,12 @@ import javax.persistence.MapKeyEnumerated; import javax.persistence.MapKeyTemporal; import javax.persistence.Temporal; import javax.persistence.TemporalType; +import java.io.Serializable; +import java.util.Calendar; +import java.util.Date; +import java.util.Properties; + +import org.jboss.logging.Logger; import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; @@ -46,9 +43,10 @@ import org.hibernate.MappingException; import org.hibernate.annotations.Nationalized; import org.hibernate.annotations.Parameter; import org.hibernate.annotations.Type; +import org.hibernate.annotations.common.reflection.ClassLoadingException; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; -import org.hibernate.annotations.common.util.ReflectHelper; +import org.hibernate.annotations.common.util.StandardClassLoaderDelegateImpl; import org.hibernate.cfg.AccessType; import org.hibernate.cfg.AttributeConverterDefinition; import org.hibernate.cfg.BinderHelper; @@ -73,7 +71,6 @@ import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.StringNVarcharType; import org.hibernate.type.WrappedMaterializedBlobType; import org.hibernate.usertype.DynamicParameterizedType; -import org.jboss.logging.Logger; /** * @author Emmanuel Bernard @@ -144,7 +141,7 @@ public class SimpleValueBinder { //TODO execute it lazily to be order safe - public void setType(XProperty property, XClass returnedClass, String declaringClassName) { + public void setType(XProperty property, XClass returnedClass, String declaringClassName, AttributeConverterDefinition attributeConverterDefinition) { if ( returnedClass == null ) { // we cannot guess anything return; @@ -302,11 +299,11 @@ public class SimpleValueBinder { defaultType = BinderHelper.isEmptyAnnotationValue( type ) ? returnedClassName : type; this.typeParameters = typeParameters; - applyAttributeConverter( property ); + applyAttributeConverter( property, attributeConverterDefinition ); } - private void applyAttributeConverter(XProperty property) { - if ( attributeConverterDefinition != null ) { + private void applyAttributeConverter(XProperty property, AttributeConverterDefinition attributeConverterDefinition) { + if ( attributeConverterDefinition == null ) { return; } @@ -337,58 +334,7 @@ public class SimpleValueBinder { return; } - // @Convert annotations take precedence if present - final Convert convertAnnotation = locateConvertAnnotation( property ); - if ( convertAnnotation != null ) { - LOG.debugf( - "Applying located @Convert AttributeConverter [%s] to attribute [%]", - convertAnnotation.converter().getName(), - property.getName() - ); - attributeConverterDefinition = mappings.locateAttributeConverter( convertAnnotation.converter() ); - if ( attributeConverterDefinition == null ) { - attributeConverterDefinition = makeAttributeConverterDefinition( convertAnnotation ); - } - } - else { - attributeConverterDefinition = locateAutoApplyAttributeConverter( property ); - } - } - - private AttributeConverterDefinition makeAttributeConverterDefinition(Convert convertAnnotation) { - try { - return new AttributeConverterDefinition( - (AttributeConverter) convertAnnotation.converter().newInstance(), - false - ); - } - catch (Exception e) { - throw new AnnotationException( "Unable to create AttributeConverter instance", e ); - } - } - - private AttributeConverterDefinition locateAutoApplyAttributeConverter(XProperty property) { - LOG.debugf( - "Attempting to locate auto-apply AttributeConverter for property [%s:%s]", - persistentClassName, - property.getName() - ); - final Class propertyType = mappings.getReflectionManager().toClass( property.getType() ); - for ( AttributeConverterDefinition attributeConverterDefinition : mappings.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; + this.attributeConverterDefinition = attributeConverterDefinition; } private boolean isAssociation() { @@ -397,218 +343,6 @@ public class SimpleValueBinder { return referencedEntityName != null; } - @SuppressWarnings("unchecked") - private Convert locateConvertAnnotation(XProperty property) { - LOG.debugf( - "Attempting to locate Convert annotation for property [%s:%s]", - persistentClassName, - property.getName() - ); - - // first look locally on the property for @Convert/@Converts - { - Convert localConvertAnnotation = property.getAnnotation( Convert.class ); - if ( localConvertAnnotation != null ) { - LOG.debugf( - "Found matching local @Convert annotation [disableConversion=%s]", - localConvertAnnotation.disableConversion() - ); - return localConvertAnnotation.disableConversion() - ? null - : localConvertAnnotation; - } - } - - { - Converts localConvertsAnnotation = property.getAnnotation( Converts.class ); - if ( localConvertsAnnotation != null ) { - for ( Convert localConvertAnnotation : localConvertsAnnotation.value() ) { - if ( isLocalMatch( localConvertAnnotation, property ) ) { - LOG.debugf( - "Found matching @Convert annotation as part local @Converts [disableConversion=%s]", - localConvertAnnotation.disableConversion() - ); - return localConvertAnnotation.disableConversion() - ? null - : localConvertAnnotation; - } - } - } - } - - if ( persistentClassName == null ) { - LOG.debug( "Persistent Class name not known during attempt to locate @Convert annotations" ); - return null; - } - - final XClass owner; - try { - final Class ownerClass = ReflectHelper.classForName( persistentClassName ); - owner = mappings.getReflectionManager().classForName( persistentClassName, ownerClass ); - } - catch (ClassNotFoundException e) { - throw new AnnotationException( "Unable to resolve Class reference during attempt to locate @Convert annotations" ); - } - - return lookForEntityDefinedConvertAnnotation( property, owner ); - } - - private Convert lookForEntityDefinedConvertAnnotation(XProperty property, XClass owner) { - if ( owner == null ) { - // we have hit the root of the entity hierarchy - return null; - } - - LOG.debugf( - "Attempting to locate any AttributeConverter defined via @Convert/@Converts on type-hierarchy [%s] to apply to attribute [%s]", - owner.getName(), - property.getName() - ); - - { - Convert convertAnnotation = owner.getAnnotation( Convert.class ); - if ( convertAnnotation != null && isMatch( convertAnnotation, property ) ) { - LOG.debugf( - "Found matching @Convert annotation [disableConversion=%s]", - convertAnnotation.disableConversion() - ); - return convertAnnotation.disableConversion() ? null : convertAnnotation; - } - } - - { - Converts convertsAnnotation = owner.getAnnotation( Converts.class ); - if ( convertsAnnotation != null ) { - for ( Convert convertAnnotation : convertsAnnotation.value() ) { - if ( isMatch( convertAnnotation, property ) ) { - LOG.debugf( - "Found matching @Convert annotation as part @Converts [disableConversion=%s]", - convertAnnotation.disableConversion() - ); - return convertAnnotation.disableConversion() ? null : convertAnnotation; - } - } - } - } - - // finally, look on superclass - return lookForEntityDefinedConvertAnnotation( property, owner.getSuperclass() ); - } - - @SuppressWarnings("unchecked") - private boolean isLocalMatch(Convert convertAnnotation, XProperty property) { - if ( StringHelper.isEmpty( convertAnnotation.attributeName() ) ) { - return isTypeMatch( convertAnnotation.converter(), property ); - } - - return property.getName().equals( convertAnnotation.attributeName() ) - && isTypeMatch( convertAnnotation.converter(), property ); - } - - @SuppressWarnings("unchecked") - private boolean isMatch(Convert convertAnnotation, XProperty property) { - return property.getName().equals( convertAnnotation.attributeName() ); -// return property.getName().equals( convertAnnotation.attributeName() ) -// && isTypeMatch( convertAnnotation.converter(), property ); - } - - private boolean isTypeMatch(Class attributeConverterClass, XProperty property) { - return areTypeMatch( - extractEntityAttributeType( attributeConverterClass ), - mappings.getReflectionManager().toClass( property.getType() ) - ); - } - - private Class extractEntityAttributeType(Class attributeConverterClass) { - // this is duplicated in SimpleValue... - final TypeVariable[] attributeConverterTypeInformation = attributeConverterClass.getTypeParameters(); - if ( attributeConverterTypeInformation == null || attributeConverterTypeInformation.length < 2 ) { - throw new AnnotationException( - "AttributeConverter [" + attributeConverterClass.getName() - + "] did not retain parameterized type information" - ); - } - - if ( attributeConverterTypeInformation.length > 2 ) { - LOG.debug( - "AttributeConverter [" + attributeConverterClass.getName() - + "] specified more than 2 parameterized types" - ); - } - final Class entityAttributeJavaType = extractType( attributeConverterTypeInformation[0] ); - if ( entityAttributeJavaType == null ) { - throw new AnnotationException( - "Could not determine 'entity attribute' type from given AttributeConverter [" + - attributeConverterClass.getName() + "]" - ); - } - return entityAttributeJavaType; - } - - private Class extractType(TypeVariable typeVariable) { - java.lang.reflect.Type[] boundTypes = typeVariable.getBounds(); - if ( boundTypes == null || boundTypes.length != 1 ) { - return null; - } - - return (Class) boundTypes[0]; - } - - private boolean areTypeMatch(Class converterDefinedType, Class propertyType) { - if ( converterDefinedType == null ) { - throw new AnnotationException( "AttributeConverter defined java type cannot be null" ); - } - if ( propertyType == null ) { - throw new AnnotationException( "Property defined java type cannot be null" ); - } - - return converterDefinedType.equals( propertyType ) - || arePrimitiveWrapperEquivalents( converterDefinedType, propertyType ); - } - - private boolean arePrimitiveWrapperEquivalents(Class converterDefinedType, Class propertyType) { - if ( converterDefinedType.isPrimitive() ) { - return getWrapperEquivalent( converterDefinedType ).equals( propertyType ); - } - else if ( propertyType.isPrimitive() ) { - return getWrapperEquivalent( propertyType ).equals( converterDefinedType ); - } - return false; - } - - private static Class getWrapperEquivalent(Class primitive) { - if ( ! primitive.isPrimitive() ) { - throw new AssertionFailure( "Passed type for which to locate wrapper equivalent was not a primitive" ); - } - - if ( boolean.class.equals( primitive ) ) { - return Boolean.class; - } - else if ( char.class.equals( primitive ) ) { - return Character.class; - } - else if ( byte.class.equals( primitive ) ) { - return Byte.class; - } - else if ( short.class.equals( primitive ) ) { - return Short.class; - } - else if ( int.class.equals( primitive ) ) { - return Integer.class; - } - else if ( long.class.equals( primitive ) ) { - return Long.class; - } - else if ( float.class.equals( primitive ) ) { - return Float.class; - } - else if ( double.class.equals( primitive ) ) { - return Double.class; - } - - throw new AssertionFailure( "Unexpected primitive type (VOID most likely) passed to getWrapperEquivalent" ); - } - private TemporalType getTemporalType(XProperty property) { if ( key ) { MapKeyTemporal ann = property.getAnnotation( MapKeyTemporal.class ); @@ -754,7 +488,7 @@ public class SimpleValueBinder { if ( simpleValue.getTypeName() != null && simpleValue.getTypeName().length() > 0 && simpleValue.getMappings().getTypeResolver().basic( simpleValue.getTypeName() ) == null ) { try { - Class typeClass = ReflectHelper.classForName( simpleValue.getTypeName() ); + Class typeClass = StandardClassLoaderDelegateImpl.INSTANCE.classForName( simpleValue.getTypeName() ); if ( typeClass != null && DynamicParameterizedType.class.isAssignableFrom( typeClass ) ) { Properties parameters = simpleValue.getTypeParameters(); @@ -772,8 +506,8 @@ public class SimpleValueBinder { simpleValue.setTypeParameters( parameters ); } } - catch ( ClassNotFoundException cnfe ) { - throw new MappingException( "Could not determine type for: " + simpleValue.getTypeName(), cnfe ); + catch (ClassLoadingException e) { + throw new MappingException( "Could not determine type for: " + simpleValue.getTypeName(), e ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java index 716cdb0704..528341bd70 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java @@ -80,7 +80,7 @@ public class SimpleValue implements KeyValue { private Properties typeParameters; private boolean cascadeDeleteEnabled; - private AttributeConverterDefinition jpaAttributeConverterDefinition; + private AttributeConverterDefinition attributeConverterDefinition; private Type type; public SimpleValue(Mappings mappings) { @@ -354,7 +354,7 @@ public class SimpleValue implements KeyValue { return; } - if ( jpaAttributeConverterDefinition == null ) { + if ( attributeConverterDefinition == 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...) @@ -405,8 +405,8 @@ public class SimpleValue implements KeyValue { private Type buildAttributeConverterTypeAdapter() { // todo : validate the number of columns present here? - final Class entityAttributeJavaType = jpaAttributeConverterDefinition.getEntityAttributeType(); - final Class databaseColumnJavaType = jpaAttributeConverterDefinition.getDatabaseColumnType(); + final Class entityAttributeJavaType = attributeConverterDefinition.getEntityAttributeType(); + final Class databaseColumnJavaType = attributeConverterDefinition.getDatabaseColumnType(); // resolve the JavaTypeDescriptor ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -430,7 +430,7 @@ 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( - jpaAttributeConverterDefinition.getAttributeConverter(), + attributeConverterDefinition.getAttributeConverter(), sqlTypeDescriptor, intermediateJavaTypeDescriptor ); @@ -444,7 +444,7 @@ public class SimpleValue implements KeyValue { ); return new AttributeConverterTypeAdapter( name, - jpaAttributeConverterDefinition.getAttributeConverter(), + attributeConverterDefinition.getAttributeConverter(), sqlTypeDescriptorAdapter, entityAttributeJavaTypeDescriptor ); @@ -486,8 +486,8 @@ public class SimpleValue implements KeyValue { return getColumnInsertability(); } - public void setJpaAttributeConverterDefinition(AttributeConverterDefinition jpaAttributeConverterDefinition) { - this.jpaAttributeConverterDefinition = jpaAttributeConverterDefinition; + public void setJpaAttributeConverterDefinition(AttributeConverterDefinition attributeConverterDefinition) { + this.attributeConverterDefinition = attributeConverterDefinition; } private void createParameterImpl() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/AttributeConverterTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/AttributeConverterTest.java index 824e4b54f2..02d0f10dcb 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/AttributeConverterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/AttributeConverterTest.java @@ -142,7 +142,6 @@ public class AttributeConverterTest extends BaseUnitTestCase { } @Test - @FailureExpected( jiraKey = "HHH-8449" ) public void testBasicConverterDisableApplication() { Configuration cfg = new Configuration(); cfg.addAttributeConverter( StringClobConverter.class, true ); diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/convert/SimpleEmbeddableOverriddenConverterTest.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/convert/SimpleEmbeddableOverriddenConverterTest.java new file mode 100644 index 0000000000..ccd331ba14 --- /dev/null +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/convert/SimpleEmbeddableOverriddenConverterTest.java @@ -0,0 +1,120 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.test.convert; + +import javax.persistence.Convert; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.jpa.boot.spi.Bootstrap; +import org.hibernate.jpa.test.PersistenceUnitDescriptorAdapter; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.type.CompositeType; +import org.hibernate.type.StringType; +import org.hibernate.type.Type; + +import org.junit.Test; + +import org.hibernate.testing.junit4.BaseUnitTestCase; + +import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; + +/** + * Tests MappedSuperclass/Entity overriding of Convert definitions + * + * @author Steve Ebersole + */ +public class SimpleEmbeddableOverriddenConverterTest extends BaseUnitTestCase { + /** + * Test outcome of annotations exclusively. + */ + @Test + public void testSimpleConvertOverrides() { + final PersistenceUnitDescriptorAdapter pu = new PersistenceUnitDescriptorAdapter() { + @Override + public List getManagedClassNames() { + return Arrays.asList( Person.class.getName() ); + } + }; + + final Map settings = new HashMap(); +// settings.put( AvailableSettings.HBM2DDL_AUTO, "create-drop" ); + + EntityManagerFactory emf = Bootstrap.getEntityManagerFactoryBuilder( pu, settings ).build(); + + final SessionFactoryImplementor sfi = emf.unwrap( SessionFactoryImplementor.class ); + try { + final EntityPersister ep = sfi.getEntityPersister( Person.class.getName() ); + + CompositeType homeAddressType = assertTyping( CompositeType.class, ep.getPropertyType( "homeAddress" ) ); + Type homeAddressCityType = findCompositeAttributeType( homeAddressType, "city" ); + assertTyping( StringType.class, homeAddressCityType ); + } + finally { + emf.close(); + } + } + + public Type findCompositeAttributeType(CompositeType compositeType, String attributeName) { + int pos = 0; + for ( String name : compositeType.getPropertyNames() ) { + if ( name.equals( attributeName ) ) { + break; + } + pos++; + } + + if ( pos >= compositeType.getPropertyNames().length ) { + throw new IllegalStateException( "Could not locate attribute index for [" + attributeName + "] in composite" ); + } + + return compositeType.getSubtypes()[pos]; + } + + @Embeddable + public static class Address { + public String street; + @Convert(converter = SillyStringConverter.class) + public String city; + } + + @Entity( name="Person" ) + public static class Person { + @Id + public Integer id; + @Embedded + @Convert( attributeName = "city", disableConversion = true ) + public Address homeAddress; + } +} diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/convert/SimpleOverriddenConverterTest.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/convert/SimpleOverriddenConverterTest.java index a7c14b332b..134088ca3e 100644 --- a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/convert/SimpleOverriddenConverterTest.java +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/convert/SimpleOverriddenConverterTest.java @@ -43,7 +43,6 @@ import org.hibernate.type.Type; import org.junit.Test; -import org.hibernate.testing.FailureExpected; import org.hibernate.testing.junit4.BaseUnitTestCase; import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; @@ -58,7 +57,6 @@ public class SimpleOverriddenConverterTest extends BaseUnitTestCase { * Test outcome of annotations exclusively. */ @Test - @FailureExpected( jiraKey = "HHH-8449" ) public void testSimpleConvertOverrides() { final PersistenceUnitDescriptorAdapter pu = new PersistenceUnitDescriptorAdapter() { @Override diff --git a/libraries.gradle b/libraries.gradle index 3d474567c9..9464f2287d 100644 --- a/libraries.gradle +++ b/libraries.gradle @@ -43,7 +43,7 @@ ext { // Annotations commons_annotations: - 'org.hibernate.common:hibernate-commons-annotations:4.0.2.Final@jar', + 'org.hibernate.common:hibernate-commons-annotations:4.0.3.Final@jar', jandex: 'org.jboss:jandex:1.1.0.Alpha1', classmate: 'com.fasterxml:classmate:0.8.0',