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 1b792fc609..1e594fa390 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -2495,6 +2495,7 @@ public final class AnnotationBinder { value.setPersistentClassName( persistentClassName ); value.setMappings( mappings ); value.setType( inferredData.getProperty(), inferredData.getClassOrElement() ); + value.setAccessType( propertyAccessor ); id = value.make(); } rootClass.setIdentifier( id ); 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 5234bde5ed..c35285d4f5 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 @@ -1289,6 +1289,8 @@ public abstract class CollectionBinder { } elementBinder.setColumns( elementColumns ); elementBinder.setType( property, elementClass ); + elementBinder.setPersistentClassName( propertyHolder.getEntityName() ); + elementBinder.setAccessType( accessType ); collValue.setElement( elementBinder.make() ); String orderBy = adjustUserSuppliedValueCollectionOrderingFragment( hqlOrderBy ); if ( orderBy != null ) { 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 a21074c3be..d62ddc3fb0 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 @@ -210,26 +210,25 @@ public class MapBinder extends CollectionBinder { } } + PersistentClass owner = mapValue.getOwner(); + AccessType accessType; + // FIXME support @Access for collection of elements + // String accessType = access != null ? access.value() : null; + if ( owner.getIdentifierProperty() != null ) { + accessType = owner.getIdentifierProperty().getPropertyAccessorName().equals( "property" ) ? AccessType.PROPERTY + : AccessType.FIELD; + } + else if ( owner.getIdentifierMapper() != null && owner.getIdentifierMapper().getPropertySpan() > 0 ) { + Property prop = (Property) owner.getIdentifierMapper().getPropertyIterator().next(); + accessType = prop.getPropertyAccessorName().equals( "property" ) ? AccessType.PROPERTY + : AccessType.FIELD; + } + else { + throw new AssertionFailure( "Unable to guess collection property accessor name" ); + } + if ( AnnotatedClassType.EMBEDDABLE.equals( classType ) ) { EntityBinder entityBinder = new EntityBinder(); - PersistentClass owner = mapValue.getOwner(); - boolean isPropertyAnnotated; - //FIXME support @Access for collection of elements - //String accessType = access != null ? access.value() : null; - if ( owner.getIdentifierProperty() != null ) { - isPropertyAnnotated = owner.getIdentifierProperty() - .getPropertyAccessorName() - .equals( "property" ); - } - else - if ( owner.getIdentifierMapper() != null && owner.getIdentifierMapper().getPropertySpan() > 0 ) { - Property prop = (Property) owner.getIdentifierMapper().getPropertyIterator().next(); - isPropertyAnnotated = prop.getPropertyAccessorName().equals( "property" ); - } - else { - throw new AssertionFailure( "Unable to guess collection property accessor name" ); - } - PropertyData inferredData; if ( isHibernateExtensionMapping() ) { @@ -242,7 +241,7 @@ public class MapBinder extends CollectionBinder { //TODO be smart with isNullable Component component = AnnotationBinder.fillComponent( - holder, inferredData, isPropertyAnnotated ? AccessType.PROPERTY : AccessType.FIELD, true, + holder, inferredData, accessType, true, entityBinder, false, false, true, mappings, inheritanceStatePerClass ); @@ -285,6 +284,8 @@ public class MapBinder extends CollectionBinder { else { elementBinder.setType( property, elementClass ); } + elementBinder.setPersistentClassName( propertyHolder.getEntityName() ); + elementBinder.setAccessType( accessType ); mapValue.setIndex( elementBinder.make() ); } } 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 84d30b332c..b4251816b7 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 @@ -188,6 +188,7 @@ public class PropertyBinder { simpleValueBinder.setType( property, returnedClass ); simpleValueBinder.setMappings( mappings ); simpleValueBinder.setReferencedEntityName( referencedEntityName ); + simpleValueBinder.setAccessType( accessType ); SimpleValue propertyValue = simpleValueBinder.make(); setValue( propertyValue ); return makeProperty(); 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 c9a321b138..d522580f2f 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 @@ -24,10 +24,10 @@ package org.hibernate.cfg.annotations; import java.io.Serializable; -import java.sql.Types; import java.util.Calendar; import java.util.Date; import java.util.Properties; + import javax.persistence.Enumerated; import javax.persistence.Lob; import javax.persistence.MapKeyEnumerated; @@ -35,14 +35,14 @@ import javax.persistence.MapKeyTemporal; import javax.persistence.Temporal; import javax.persistence.TemporalType; -import org.jboss.logging.Logger; - import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; +import org.hibernate.MappingException; import org.hibernate.annotations.Parameter; import org.hibernate.annotations.Type; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; +import org.hibernate.cfg.AccessType; import org.hibernate.cfg.BinderHelper; import org.hibernate.cfg.Ejb3Column; import org.hibernate.cfg.Ejb3JoinColumn; @@ -51,6 +51,7 @@ import org.hibernate.cfg.NotYetImplementedException; import org.hibernate.cfg.PkDrivenByDefaultMapsIdSecondPass; import org.hibernate.cfg.SetSimpleValueTypeSecondPass; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.StringHelper; import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.Table; @@ -60,6 +61,8 @@ import org.hibernate.type.PrimitiveCharacterArrayClobType; import org.hibernate.type.SerializableToBlobType; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.WrappedMaterializedBlobType; +import org.hibernate.usertype.DynamicParameterizedType; +import org.jboss.logging.Logger; /** * @author Emmanuel Bernard @@ -72,6 +75,7 @@ public class SimpleValueBinder { private Ejb3Column[] columns; private String persistentClassName; private String explicitType = ""; + private String defaultType = ""; private Properties typeParameters = new Properties(); private Mappings mappings; private Table table; @@ -81,6 +85,8 @@ public class SimpleValueBinder { //is a Map key private boolean key; private String referencedEntityName; + private XProperty xproperty; + private AccessType accessType; public void setReferencedEntityName(String referencedEntityName) { this.referencedEntityName = referencedEntityName; @@ -104,6 +110,10 @@ public class SimpleValueBinder { public void setReturnedClassName(String returnedClassName) { this.returnedClassName = returnedClassName; + + if ( defaultType.length() == 0 ) { + defaultType = returnedClassName; + } } public void setTable(Table table) { @@ -131,10 +141,17 @@ public class SimpleValueBinder { returnedClassOrElement = property.getElementClass(); isArray = true; } + this.xproperty = property; Properties typeParameters = this.typeParameters; typeParameters.clear(); String type = BinderHelper.ANNOTATION_STRING_DEFAULT; - if ( ( !key && property.isAnnotationPresent( Temporal.class ) ) + + Type annType = property.getAnnotation( Type.class ); + if ( annType != null ) { + setExplicitType( annType ); + type = explicitType; + } + else if ( ( !key && property.isAnnotationPresent( Temporal.class ) ) || ( key && property.isAnnotationPresent( MapKeyTemporal.class ) ) ) { boolean isDate; @@ -170,6 +187,7 @@ public class SimpleValueBinder { default: throw new AssertionFailure( "Unknown temporal type: " + temporalType ); } + explicitType = type; } else if ( property.isAnnotationPresent( Lob.class ) ) { @@ -207,59 +225,27 @@ public class SimpleValueBinder { else { type = "blob"; } + explicitType = type; } - //implicit type will check basic types and Serializable classes + else if ( ( !key && property.isAnnotationPresent( Enumerated.class ) ) + || ( key && property.isAnnotationPresent( MapKeyEnumerated.class ) ) ) { + type = EnumType.class.getName(); + explicitType = type; + } + + // implicit type will check basic types and Serializable classes if ( columns == null ) { throw new AssertionFailure( "SimpleValueBinder.setColumns should be set before SimpleValueBinder.setType" ); } + if ( BinderHelper.ANNOTATION_STRING_DEFAULT.equals( type ) ) { if ( returnedClassOrElement.isEnum() ) { type = EnumType.class.getName(); - typeParameters = new Properties(); - typeParameters.setProperty( EnumType.ENUM, returnedClassOrElement.getName() ); - String schema = columns[0].getTable().getSchema(); - schema = schema == null ? "" : schema; - String catalog = columns[0].getTable().getCatalog(); - catalog = catalog == null ? "" : catalog; - typeParameters.setProperty( EnumType.SCHEMA, schema ); - typeParameters.setProperty( EnumType.CATALOG, catalog ); - typeParameters.setProperty( EnumType.TABLE, columns[0].getTable().getName() ); - typeParameters.setProperty( EnumType.COLUMN, columns[0].getName() ); - javax.persistence.EnumType enumType = getEnumType( property ); - if ( enumType != null ) { - if ( javax.persistence.EnumType.ORDINAL.equals( enumType ) ) { - typeParameters.setProperty( EnumType.TYPE, String.valueOf( Types.INTEGER ) ); - } - else if ( javax.persistence.EnumType.STRING.equals( enumType ) ) { - typeParameters.setProperty( EnumType.TYPE, String.valueOf( Types.VARCHAR ) ); - } - else { - throw new AssertionFailure( "Unknown EnumType: " + enumType ); - } - } } } - explicitType = type; - this.typeParameters = typeParameters; - Type annType = property.getAnnotation( Type.class ); - setExplicitType( annType ); - } - private javax.persistence.EnumType getEnumType(XProperty property) { - javax.persistence.EnumType enumType = null; - if ( key ) { - MapKeyEnumerated enumAnn = property.getAnnotation( MapKeyEnumerated.class ); - if ( enumAnn != null ) { - enumType = enumAnn.value(); - } - } - else { - Enumerated enumAnn = property.getAnnotation( Enumerated.class ); - if ( enumAnn != null ) { - enumType = enumAnn.value(); - } - } - return enumType; + defaultType = BinderHelper.isEmptyAnnotationValue( type ) ? returnedClassName : type; + this.typeParameters = typeParameters; } private TemporalType getTemporalType(XProperty property) { @@ -341,17 +327,37 @@ public class SimpleValueBinder { LOG.debugf( "Setting SimpleValue typeName for %s", propertyName ); - String type = BinderHelper.isEmptyAnnotationValue( explicitType ) ? returnedClassName : explicitType; - org.hibernate.mapping.TypeDef typeDef = mappings.getTypeDef( type ); + String type; + org.hibernate.mapping.TypeDef typeDef; + + if ( !BinderHelper.isEmptyAnnotationValue( explicitType ) ) { + type = explicitType; + typeDef = mappings.getTypeDef( type ); + } + else { + // try implicit type + org.hibernate.mapping.TypeDef implicitTypeDef = mappings.getTypeDef( returnedClassName ); + if ( implicitTypeDef != null ) { + typeDef = implicitTypeDef; + type = returnedClassName; + } + else { + typeDef = mappings.getTypeDef( defaultType ); + type = defaultType; + } + } + if ( typeDef != null ) { type = typeDef.getTypeClass(); simpleValue.setTypeParameters( typeDef.getParameters() ); } + if ( typeParameters != null && typeParameters.size() != 0 ) { //explicit type params takes precedence over type def params simpleValue.setTypeParameters( typeParameters ); } simpleValue.setTypeName( type ); + if ( persistentClassName != null ) { simpleValue.setTypeUsingReflection( persistentClassName, propertyName ); } @@ -364,9 +370,42 @@ public class SimpleValueBinder { if ( timeStampVersionType != null ) { simpleValue.setTypeName( timeStampVersionType ); } + + if ( simpleValue.getTypeName() != null && simpleValue.getTypeName().length() > 0 + && simpleValue.getMappings().getTypeResolver().basic( simpleValue.getTypeName() ) == null ) { + try { + Class typeClass = ReflectHelper.classForName( simpleValue.getTypeName() ); + + if ( typeClass != null && DynamicParameterizedType.class.isAssignableFrom( typeClass ) ) { + Properties parameters = simpleValue.getTypeParameters(); + if ( parameters == null ) { + parameters = new Properties(); + } + parameters.put( DynamicParameterizedType.IS_DYNAMIC, Boolean.toString( true ) ); + parameters.put( DynamicParameterizedType.RETURNED_CLASS, returnedClassName ); + parameters.put( DynamicParameterizedType.IS_PRIMARY_KEY, Boolean.toString( key ) ); + + parameters.put( DynamicParameterizedType.ENTITY, persistentClassName ); + parameters.put( DynamicParameterizedType.PROPERTY, xproperty.getName() ); + parameters.put( DynamicParameterizedType.ACCESS_TYPE, accessType.getType() ); + simpleValue.setTypeParameters( parameters ); + } + } + catch ( ClassNotFoundException cnfe ) { + throw new MappingException( "Could not determine type for: " + simpleValue.getTypeName(), cnfe ); + } + } } public void setKey(boolean key) { this.key = key; } + + public AccessType getAccessType() { + return accessType; + } + + public void setAccessType(AccessType accessType) { + this.accessType = accessType; + } } 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 37e9467e05..984645a9e0 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java @@ -22,6 +22,8 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.mapping; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -29,6 +31,7 @@ import java.util.Properties; import org.hibernate.FetchMode; import org.hibernate.MappingException; +import org.hibernate.cfg.AccessType; import org.hibernate.cfg.Environment; import org.hibernate.cfg.Mappings; import org.hibernate.dialect.Dialect; @@ -38,7 +41,9 @@ import org.hibernate.id.IdentityGenerator; import org.hibernate.id.PersistentIdentifierGenerator; import org.hibernate.id.factory.IdentifierGeneratorFactory; import org.hibernate.internal.util.ReflectHelper; +import org.hibernate.property.DirectPropertyAccessor; import org.hibernate.type.Type; +import org.hibernate.usertype.DynamicParameterizedType; /** * Any value that maps to columns. @@ -290,19 +295,25 @@ public class SimpleValue implements KeyValue { } public Type getType() throws MappingException { - if (typeName==null) { - throw new MappingException("No type name"); + if ( typeName == null ) { + throw new MappingException( "No type name" ); } - Type result = mappings.getTypeResolver().heuristicType(typeName, typeParameters); - if (result==null) { + if ( typeParameters != null + && Boolean.valueOf( typeParameters.getProperty( DynamicParameterizedType.IS_DYNAMIC ) ) + && typeParameters.get( DynamicParameterizedType.PARAMETER_TYPE ) == null ) { + createParameterImpl(); + } + + Type result = mappings.getTypeResolver().heuristicType( typeName, typeParameters ); + if ( result == null ) { String msg = "Could not determine type for: " + typeName; - if(table != null){ + if ( table != null ) { msg += ", at table: " + table.getName(); } - if(columns!=null && columns.size()>0) { + if ( columns != null && columns.size() > 0 ) { msg += ", for columns: " + columns; } - throw new MappingException(msg); + throw new MappingException( msg ); } return result; } @@ -351,4 +362,97 @@ public class SimpleValue implements KeyValue { public boolean[] getColumnUpdateability() { return getColumnInsertability(); } + + private void createParameterImpl() { + try { + String[] columnsNames = new String[columns.size()]; + for ( int i = 0; i < columns.size(); i++ ) { + columnsNames[i] = ( (Column) columns.get( i ) ).getName(); + } + + AccessType accessType = AccessType.getAccessStrategy( typeParameters + .getProperty( DynamicParameterizedType.ACCESS_TYPE ) ); + final Class classEntity = ReflectHelper.classForName( typeParameters + .getProperty( DynamicParameterizedType.ENTITY ) ); + final String propertyName = typeParameters.getProperty( DynamicParameterizedType.PROPERTY ); + + Annotation[] annotations; + if ( accessType == AccessType.FIELD ) { + annotations = ( (Field) new DirectPropertyAccessor().getGetter( classEntity, propertyName ).getMember() ) + .getAnnotations(); + + } + else { + annotations = ReflectHelper.getGetter( classEntity, propertyName ).getMethod().getAnnotations(); + } + + typeParameters.put( + DynamicParameterizedType.PARAMETER_TYPE, + new ParameterTypeImpl( ReflectHelper.classForName( typeParameters + .getProperty( DynamicParameterizedType.RETURNED_CLASS ) ), annotations, table.getCatalog(), + table.getSchema(), table.getName(), Boolean.valueOf( typeParameters + .getProperty( DynamicParameterizedType.IS_PRIMARY_KEY ) ), columnsNames ) ); + + } + catch ( ClassNotFoundException cnfe ) { + throw new MappingException( "Could not create DynamicParameterizedType for type: " + typeName, cnfe ); + } + } + + private final class ParameterTypeImpl implements DynamicParameterizedType.ParameterType { + + private final Class returnedClass; + private final Annotation[] annotationsMethod; + private final String catalog; + private final String schema; + private final String table; + private final boolean primaryKey; + private final String[] columns; + + private ParameterTypeImpl(Class returnedClass, Annotation[] annotationsMethod, String catalog, String schema, + String table, boolean primaryKey, String[] columns) { + this.returnedClass = returnedClass; + this.annotationsMethod = annotationsMethod; + this.catalog = catalog; + this.schema = schema; + this.table = table; + this.primaryKey = primaryKey; + this.columns = columns; + } + + @Override + public Class getReturnedClass() { + return returnedClass; + } + + @Override + public Annotation[] getAnnotationsMethod() { + return annotationsMethod; + } + + @Override + public String getCatalog() { + return catalog; + } + + @Override + public String getSchema() { + return schema; + } + + @Override + public String getTable() { + return table; + } + + @Override + public boolean isPrimaryKey() { + return primaryKey; + } + + @Override + public String[] getColumns() { + return columns; + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/EnumType.java b/hibernate-core/src/main/java/org/hibernate/type/EnumType.java index 7c87540def..0034e4f951 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EnumType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EnumType.java @@ -24,20 +24,24 @@ package org.hibernate.type; import java.io.Serializable; +import java.lang.annotation.Annotation; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.Properties; -import org.jboss.logging.Logger; +import javax.persistence.Enumerated; +import javax.persistence.MapKeyEnumerated; +import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.ReflectHelper; +import org.hibernate.usertype.DynamicParameterizedType; import org.hibernate.usertype.EnhancedUserType; -import org.hibernate.usertype.ParameterizedType; +import org.jboss.logging.Logger; /** * Enum type mapper @@ -49,7 +53,7 @@ import org.hibernate.usertype.ParameterizedType; * @author Hardy Ferentschik */ @SuppressWarnings("unchecked") -public class EnumType implements EnhancedUserType, ParameterizedType, Serializable { +public class EnumType implements EnhancedUserType, DynamicParameterizedType, Serializable { private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, EnumType.class.getName()); @@ -168,17 +172,37 @@ public class EnumType implements EnhancedUserType, ParameterizedType, Serializab } public void setParameterValues(Properties parameters) { - String enumClassName = parameters.getProperty( ENUM ); - try { - enumClass = ReflectHelper.classForName( enumClassName, this.getClass() ).asSubclass( Enum.class ); - } - catch ( ClassNotFoundException exception ) { - throw new HibernateException( "Enum class not found", exception ); - } + ParameterType reader = (ParameterType) parameters.get( PARAMETER_TYPE ); - String type = parameters.getProperty( TYPE ); - if ( type != null ) { - sqlType = Integer.decode( type ); + if ( reader != null ) { + enumClass = reader.getReturnedClass().asSubclass( Enum.class ); + + javax.persistence.EnumType enumType = getEnumType( reader ); + if ( enumType != null ) { + if ( javax.persistence.EnumType.ORDINAL.equals( enumType ) ) { + sqlType = Types.INTEGER; + } + else if ( javax.persistence.EnumType.STRING.equals( enumType ) ) { + sqlType = Types.VARCHAR; + } + else { + throw new AssertionFailure( "Unknown EnumType: " + enumType ); + } + } + } + else { + String enumClassName = (String) parameters.get( ENUM ); + try { + enumClass = ReflectHelper.classForName( enumClassName, this.getClass() ).asSubclass( Enum.class ); + } + catch ( ClassNotFoundException exception ) { + throw new HibernateException( "Enum class not found", exception ); + } + + String type = (String) parameters.get( TYPE ); + if ( type != null ) { + sqlType = Integer.decode( type ); + } } } @@ -234,4 +258,30 @@ public class EnumType implements EnhancedUserType, ParameterizedType, Serializab } } } + + private javax.persistence.EnumType getEnumType(ParameterType reader) { + javax.persistence.EnumType enumType = null; + if ( reader.isPrimaryKey() ) { + MapKeyEnumerated enumAnn = getAnnotation( reader.getAnnotationsMethod(), MapKeyEnumerated.class ); + if ( enumAnn != null ) { + enumType = enumAnn.value(); + } + } + else { + Enumerated enumAnn = getAnnotation( reader.getAnnotationsMethod(), Enumerated.class ); + if ( enumAnn != null ) { + enumType = enumAnn.value(); + } + } + return enumType; + } + + private T getAnnotation(Annotation[] annotations, Class anClass) { + for ( Annotation annotation : annotations ) { + if ( anClass.isInstance( annotation ) ) { + return (T) annotation; + } + } + return null; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/usertype/DynamicParameterizedType.java b/hibernate-core/src/main/java/org/hibernate/usertype/DynamicParameterizedType.java new file mode 100644 index 0000000000..9cbcb0a96d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/usertype/DynamicParameterizedType.java @@ -0,0 +1,45 @@ +package org.hibernate.usertype; + +import java.lang.annotation.Annotation; + +/** + * {@inheritDoc} + * + * Types who implements this interface will have in the setParameterValues an + * instance of the class DynamicParameterizedType$ParameterType with + * the key PARAMETER_TYPE = "org.hibernate.type.ParameterType" + * + * The interface ParameterType provides some methods to read information + * dynamically for build the type + * + * @author Janario Oliveira + */ +public interface DynamicParameterizedType extends ParameterizedType { + public static final String PARAMETER_TYPE = "org.hibernate.type.ParameterType"; + + public static final String IS_DYNAMIC = "org.hibernate.type.ParameterType.dynamic"; + + public static final String RETURNED_CLASS = "org.hibernate.type.ParameterType.returnedClass"; + public static final String IS_PRIMARY_KEY = "org.hibernate.type.ParameterType.primaryKey"; + public static final String ENTITY = "org.hibernate.type.ParameterType.entityClass"; + public static final String PROPERTY = "org.hibernate.type.ParameterType.propertyName"; + public static final String ACCESS_TYPE = "org.hibernate.type.ParameterType.accessType"; + + public static interface ParameterType { + + public Class getReturnedClass(); + + public Annotation[] getAnnotationsMethod(); + + public String getCatalog(); + + public String getSchema(); + + public String getTable(); + + public boolean isPrimaryKey(); + + public String[] getColumns(); + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/enumerated/EntityEnum.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/enumerated/EntityEnum.java new file mode 100644 index 0000000000..8016c10abc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/enumerated/EntityEnum.java @@ -0,0 +1,93 @@ +package org.hibernate.test.annotations.enumerated; + +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.hibernate.annotations.TypeDefs; + +/** + * @author Janario Oliveira + */ +@Entity +@TypeDefs({ @TypeDef(typeClass = LastNumberType.class, defaultForType = EntityEnum.LastNumber.class) }) +public class EntityEnum { + + enum Common { + + A1, A2, B1, B2 + } + + enum FirstLetter { + + A_LETTER, B_LETTER, C_LETTER + } + + enum LastNumber { + + NUMBER_1, NUMBER_2, NUMBER_3 + } + + @Id + @GeneratedValue + private long id; + private Common ordinal; + @Enumerated(EnumType.STRING) + private Common string; + @Type(type = "org.hibernate.test.annotations.enumerated.FirstLetterType") + private FirstLetter firstLetter; + private LastNumber lastNumber; + @Enumerated(EnumType.STRING) + private LastNumber explicitOverridingImplicit; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Common getOrdinal() { + return ordinal; + } + + public void setOrdinal(Common ordinal) { + this.ordinal = ordinal; + } + + public Common getString() { + return string; + } + + public void setString(Common string) { + this.string = string; + } + + public FirstLetter getFirstLetter() { + return firstLetter; + } + + public void setFirstLetter(FirstLetter firstLetter) { + this.firstLetter = firstLetter; + } + + public LastNumber getLastNumber() { + return lastNumber; + } + + public void setLastNumber(LastNumber lastNumber) { + this.lastNumber = lastNumber; + } + + public LastNumber getExplicitOverridingImplicit() { + return explicitOverridingImplicit; + } + + public void setExplicitOverridingImplicit(LastNumber explicitOverridingImplicit) { + this.explicitOverridingImplicit = explicitOverridingImplicit; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/enumerated/EnumeratedTypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/enumerated/EnumeratedTypeTest.java new file mode 100644 index 0000000000..10e2071604 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/enumerated/EnumeratedTypeTest.java @@ -0,0 +1,335 @@ +package org.hibernate.test.annotations.enumerated; + +import java.io.Serializable; +import org.hibernate.Session; +import org.hibernate.cfg.Configuration; +import org.hibernate.criterion.Restrictions; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.type.EnumType; +import org.hibernate.type.Type; +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.hibernate.test.annotations.enumerated.EntityEnum.*; + +/** + * Test type definition for enum + * + * @author Janario Oliveira + */ +public class EnumeratedTypeTest extends BaseCoreFunctionalTestCase { + + @Test + public void testTypeDefinition() { + Configuration cfg = configuration(); + PersistentClass pc = cfg.getClassMapping( EntityEnum.class.getName() ); + + // ordinal default of EnumType + Type ordinalEnum = pc.getProperty( "ordinal" ).getType(); + assertEquals( Common.class, ordinalEnum.getReturnedClass() ); + assertEquals( EnumType.class.getName(), ordinalEnum.getName() ); + + // string defined by Enumerated(STRING) + Type stringEnum = pc.getProperty( "string" ).getType(); + assertEquals( Common.class, stringEnum.getReturnedClass() ); + assertEquals( EnumType.class.getName(), stringEnum.getName() ); + + // explicit defined by @Type + Type first = pc.getProperty( "firstLetter" ).getType(); + assertEquals( FirstLetter.class, first.getReturnedClass() ); + assertEquals( FirstLetterType.class.getName(), first.getName() ); + + // implicit defined by @TypeDef in somewhere + Type last = pc.getProperty( "lastNumber" ).getType(); + assertEquals( LastNumber.class, last.getReturnedClass() ); + assertEquals( LastNumberType.class.getName(), last.getName() ); + + // implicit defined by @TypeDef in anywhere, but overrided by Enumerated(STRING) + Type implicitOverrideExplicit = pc.getProperty( "explicitOverridingImplicit" ).getType(); + assertEquals( LastNumber.class, implicitOverrideExplicit.getReturnedClass() ); + assertEquals( EnumType.class.getName(), implicitOverrideExplicit.getName() ); + } + + @Test + public void testTypeQuery() { + Session session = openSession(); + session.getTransaction().begin(); + + // persist + EntityEnum entityEnum = new EntityEnum(); + entityEnum.setOrdinal( Common.A2 ); + Serializable id = session.save( entityEnum ); + + session.getTransaction().commit(); + session.close(); + session = openSession(); + session.getTransaction().begin(); + + // find + entityEnum = (EntityEnum) session.createQuery( "from EntityEnum ee where ee.ordinal=1" ).uniqueResult(); + assertEquals( id, entityEnum.getId() ); + assertEquals( Common.A2, entityEnum.getOrdinal() ); + // find parameter + entityEnum = (EntityEnum) session.createQuery( "from EntityEnum ee where ee.ordinal=:ordinal" ) + .setParameter( "ordinal", Common.A2 ).uniqueResult(); + assertEquals( id, entityEnum.getId() ); + assertEquals( Common.A2, entityEnum.getOrdinal() ); + // delete + assertEquals( 1, session.createSQLQuery( "DELETE FROM EntityEnum where ordinal=1" ).executeUpdate() ); + + session.getTransaction().commit(); + session.close(); + + // ************** + session = openSession(); + session.getTransaction().begin(); + + // persist + entityEnum = new EntityEnum(); + entityEnum.setString( Common.B1 ); + id = session.save( entityEnum ); + + session.getTransaction().commit(); + session.close(); + session = openSession(); + session.getTransaction().begin(); + + // find + entityEnum = (EntityEnum) session.createQuery( "from EntityEnum ee where ee.string='B1'" ).uniqueResult(); + assertEquals( id, entityEnum.getId() ); + assertEquals( Common.B1, entityEnum.getString() ); + // find parameter + entityEnum = (EntityEnum) session.createQuery( "from EntityEnum ee where ee.string=:string" ) + .setParameter( "string", Common.B1 ).uniqueResult(); + assertEquals( id, entityEnum.getId() ); + assertEquals( Common.B1, entityEnum.getString() ); + // delete + assertEquals( 1, session.createSQLQuery( "DELETE FROM EntityEnum where string='B1'" ).executeUpdate() ); + session.getTransaction().commit(); + session.close(); + + // ************** + session = openSession(); + session.getTransaction().begin(); + + // persist + entityEnum = new EntityEnum(); + entityEnum.setFirstLetter( FirstLetter.C_LETTER ); + id = session.save( entityEnum ); + + session.getTransaction().commit(); + session.close(); + session = openSession(); + session.getTransaction().begin(); + + // find + entityEnum = (EntityEnum) session.createQuery( "from EntityEnum ee where ee.firstLetter='C'" ).uniqueResult(); + assertEquals( id, entityEnum.getId() ); + assertEquals( FirstLetter.C_LETTER, entityEnum.getFirstLetter() ); + // find parameter + entityEnum = (EntityEnum) session.createQuery( "from EntityEnum ee where ee.firstLetter=:firstLetter" ) + .setParameter( "firstLetter", FirstLetter.C_LETTER ).uniqueResult(); + assertEquals( id, entityEnum.getId() ); + assertEquals( FirstLetter.C_LETTER, entityEnum.getFirstLetter() ); + // delete + assertEquals( 1, session.createSQLQuery( "DELETE FROM EntityEnum where firstLetter='C'" ).executeUpdate() ); + + session.getTransaction().commit(); + session.close(); + + // ************** + session = openSession(); + session.getTransaction().begin(); + + // persist + entityEnum = new EntityEnum(); + entityEnum.setLastNumber( LastNumber.NUMBER_1 ); + id = session.save( entityEnum ); + + session.getTransaction().commit(); + session.close(); + session = openSession(); + session.getTransaction().begin(); + + // find + entityEnum = (EntityEnum) session.createQuery( "from EntityEnum ee where ee.lastNumber='1'" ).uniqueResult(); + assertEquals( id, entityEnum.getId() ); + assertEquals( LastNumber.NUMBER_1, entityEnum.getLastNumber() ); + // find parameter + entityEnum = (EntityEnum) session.createQuery( "from EntityEnum ee where ee.lastNumber=:lastNumber" ) + .setParameter( "lastNumber", LastNumber.NUMBER_1 ).uniqueResult(); + assertEquals( id, entityEnum.getId() ); + assertEquals( LastNumber.NUMBER_1, entityEnum.getLastNumber() ); + // delete + assertEquals( 1, session.createSQLQuery( "DELETE FROM EntityEnum where lastNumber='1'" ).executeUpdate() ); + + session.getTransaction().commit(); + session.close(); + + // ************** + session = openSession(); + session.getTransaction().begin(); + + // persist + entityEnum = new EntityEnum(); + entityEnum.setExplicitOverridingImplicit( LastNumber.NUMBER_2 ); + id = session.save( entityEnum ); + + session.getTransaction().commit(); + session.close(); + session = openSession(); + session.getTransaction().begin(); + + // find + entityEnum = (EntityEnum) session.createQuery( + "from EntityEnum ee where ee.explicitOverridingImplicit='NUMBER_2'" ).uniqueResult(); + assertEquals( id, entityEnum.getId() ); + assertEquals( LastNumber.NUMBER_2, entityEnum.getExplicitOverridingImplicit() ); + // find parameter + entityEnum = (EntityEnum) session + .createQuery( "from EntityEnum ee where ee.explicitOverridingImplicit=:override" ) + .setParameter( "override", LastNumber.NUMBER_2 ).uniqueResult(); + assertEquals( id, entityEnum.getId() ); + assertEquals( LastNumber.NUMBER_2, entityEnum.getExplicitOverridingImplicit() ); + // delete + assertEquals( 1, session.createSQLQuery( "DELETE FROM EntityEnum where explicitOverridingImplicit='NUMBER_2'" ) + .executeUpdate() ); + + session.getTransaction().commit(); + session.close(); + } + + @Test + public void testTypeCriteria() { + Session session = openSession(); + session.getTransaction().begin(); + + // persist + EntityEnum entityEnum = new EntityEnum(); + entityEnum.setOrdinal( Common.A1 ); + Serializable id = session.save( entityEnum ); + + session.getTransaction().commit(); + session.close(); + session = openSession(); + session.getTransaction().begin(); + + // find + entityEnum = (EntityEnum) session.createCriteria( EntityEnum.class ) + .add( Restrictions.eq( "ordinal", Common.A1 ) ).uniqueResult(); + assertEquals( id, entityEnum.getId() ); + assertEquals( Common.A1, entityEnum.getOrdinal() ); + // delete + assertEquals( 1, session.createSQLQuery( "DELETE FROM EntityEnum where ordinal=0" ).executeUpdate() ); + + session.getTransaction().commit(); + session.close(); + + // ************** + session = openSession(); + session.getTransaction().begin(); + + // persist + entityEnum = new EntityEnum(); + entityEnum.setString( Common.B2 ); + id = session.save( entityEnum ); + + session.getTransaction().commit(); + session.close(); + session = openSession(); + session.getTransaction().begin(); + + // find + entityEnum = (EntityEnum) session.createCriteria( EntityEnum.class ) + .add( Restrictions.eq( "string", Common.B2 ) ).uniqueResult(); + assertEquals( id, entityEnum.getId() ); + assertEquals( Common.B2, entityEnum.getString() ); + // delete + assertEquals( 1, session.createSQLQuery( "DELETE FROM EntityEnum where string='B2'" ).executeUpdate() ); + + session.getTransaction().commit(); + session.close(); + + // ************** + session = openSession(); + session.getTransaction().begin(); + + // persist + entityEnum = new EntityEnum(); + entityEnum.setFirstLetter( FirstLetter.A_LETTER ); + id = session.save( entityEnum ); + + session.getTransaction().commit(); + session.close(); + session = openSession(); + session.getTransaction().begin(); + + // find + entityEnum = (EntityEnum) session.createCriteria( EntityEnum.class ) + .add( Restrictions.eq( "firstLetter", FirstLetter.A_LETTER ) ).uniqueResult(); + assertEquals( id, entityEnum.getId() ); + assertEquals( FirstLetter.A_LETTER, entityEnum.getFirstLetter() ); + // delete + assertEquals( 1, session.createSQLQuery( "DELETE FROM EntityEnum where firstLetter='A'" ).executeUpdate() ); + + session.getTransaction().commit(); + session.close(); + + // ************** + session = openSession(); + session.getTransaction().begin(); + + // persist + entityEnum = new EntityEnum(); + entityEnum.setLastNumber( LastNumber.NUMBER_3 ); + id = session.save( entityEnum ); + + session.getTransaction().commit(); + session.close(); + session = openSession(); + session.getTransaction().begin(); + + // find + entityEnum = (EntityEnum) session.createCriteria( EntityEnum.class ) + .add( Restrictions.eq( "lastNumber", LastNumber.NUMBER_3 ) ).uniqueResult(); + assertEquals( id, entityEnum.getId() ); + assertEquals( LastNumber.NUMBER_3, entityEnum.getLastNumber() ); + // delete + assertEquals( 1, session.createSQLQuery( "DELETE FROM EntityEnum where lastNumber='3'" ).executeUpdate() ); + + session.getTransaction().commit(); + session.close(); + + // ************** + session = openSession(); + session.getTransaction().begin(); + + // persist + entityEnum = new EntityEnum(); + entityEnum.setExplicitOverridingImplicit( LastNumber.NUMBER_2 ); + id = session.save( entityEnum ); + + session.getTransaction().commit(); + session.close(); + session = openSession(); + session.getTransaction().begin(); + + // find + entityEnum = (EntityEnum) session.createCriteria( EntityEnum.class ) + .add( Restrictions.eq( "explicitOverridingImplicit", LastNumber.NUMBER_2 ) ).uniqueResult(); + assertEquals( id, entityEnum.getId() ); + assertEquals( LastNumber.NUMBER_2, entityEnum.getExplicitOverridingImplicit() ); + // delete + assertEquals( 1, session.createSQLQuery( "DELETE FROM EntityEnum where explicitOverridingImplicit='NUMBER_2'" ) + .executeUpdate() ); + + session.getTransaction().commit(); + session.close(); + + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { EntityEnum.class }; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/enumerated/FirstLetterType.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/enumerated/FirstLetterType.java new file mode 100644 index 0000000000..83ab61f2ee --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/enumerated/FirstLetterType.java @@ -0,0 +1,41 @@ +package org.hibernate.test.annotations.enumerated; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; + +/** + * @author Janario Oliveira + */ +public class FirstLetterType extends org.hibernate.type.EnumType { + + @Override + public int[] sqlTypes() { + return new int[] { Types.VARCHAR }; + } + + @Override + public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) + throws HibernateException, SQLException { + String persistValue = (String) rs.getObject( names[0] ); + if ( rs.wasNull() ) { + return null; + } + return Enum.valueOf( returnedClass(), persistValue + "_LETTER" ); + } + + @Override + public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) + throws HibernateException, SQLException { + if ( value == null ) { + st.setNull( index, sqlTypes()[0] ); + } + else { + String enumString = ( (Enum) value ).name(); + st.setObject( index, enumString.charAt( 0 ), sqlTypes()[0] ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/enumerated/LastNumberType.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/enumerated/LastNumberType.java new file mode 100644 index 0000000000..65e38525f9 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/enumerated/LastNumberType.java @@ -0,0 +1,42 @@ +package org.hibernate.test.annotations.enumerated; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; + +/** + * @author Janario Oliveira + */ +public class LastNumberType extends org.hibernate.type.EnumType { + + @Override + public int[] sqlTypes() { + return new int[] { Types.VARCHAR }; + } + + @Override + public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) + throws HibernateException, SQLException { + String persistValue = (String) rs.getObject( names[0] ); + if ( rs.wasNull() ) { + return null; + } + return Enum.valueOf( returnedClass(), "NUMBER_" + persistValue ); + } + + @Override + public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) + throws HibernateException, SQLException { + if ( value == null ) { + st.setNull( index, sqlTypes()[0] ); + } + else { + + String enumString = ( (Enum) value ).name(); + st.setObject( index, enumString.charAt( enumString.length() - 1 ), sqlTypes()[0] ); + } + } +} diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/BasicMetadataGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/BasicMetadataGenerator.java index c575deac75..2ce167ab9a 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/BasicMetadataGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/BasicMetadataGenerator.java @@ -64,12 +64,17 @@ public final class BasicMetadataGenerator { Element type_mapping = prop_mapping.addElement("type"); type_mapping.addAttribute("name", typeName); - for (java.util.Map.Entry paramKeyValue : typeParameters.entrySet()) { - Element type_param = type_mapping.addElement("param"); - type_param.addAttribute("name", (String) paramKeyValue.getKey()); - type_param.setText((String) paramKeyValue.getValue()); - } - } + for ( Object object : typeParameters.keySet() ) { + String keyType = (String) object; + String property = typeParameters.getProperty( keyType ); + + if ( property != null ) { + Element type_param = type_mapping.addElement( "param" ); + type_param.addAttribute( "name", keyType ); + type_param.setText( property ); + } + } + } } // A null mapper means that we only want to add xml mappings