diff --git a/annotations/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/annotations/src/main/java/org/hibernate/cfg/AnnotationBinder.java index f8a19defee..665ef33cd2 100644 --- a/annotations/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/annotations/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -56,6 +56,7 @@ import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; import javax.persistence.MapKey; import javax.persistence.MappedSuperclass; +import javax.persistence.MapsId; import javax.persistence.NamedNativeQueries; import javax.persistence.NamedNativeQuery; import javax.persistence.NamedQueries; @@ -891,7 +892,7 @@ public final class AnnotationBinder { boolean hasIdentifier = false; for ( int index = 0; index < deep; index++ ) { - PropertyContainer properyContainer = new PropertyContainer( classesToProcess.get( index ) ); + PropertyContainer properyContainer = new PropertyContainer( classesToProcess.get( index ), clazzToProcess ); boolean currentHasIdentifier = addElementsOfClass( elements, accessType, properyContainer, mappings ); hasIdentifier = hasIdentifier || currentHasIdentifier; } @@ -1116,7 +1117,7 @@ public final class AnnotationBinder { Collection properties = propertyContainer.getProperties( accessType ); for ( XProperty p : properties ) { final boolean currentHasIdentifier = addProperty( - propertyContainer.getXClass(), p, elements, accessType.getType(), mappings + propertyContainer, p, elements, accessType.getType(), mappings ); hasIdentifier = hasIdentifier || currentHasIdentifier; } @@ -1124,9 +1125,11 @@ public final class AnnotationBinder { } private static boolean addProperty( - XClass declaringClass, XProperty property, List annElts, + PropertyContainer propertyContainer, XProperty property, List annElts, String propertyAccessor, ExtendedMappings mappings ) { + final XClass declaringClass = propertyContainer.getDeclaringClass(); + final XClass entity = propertyContainer.getEntityAtStake(); boolean hasIdentifier; PropertyData propertyAnnotatedElement = new PropertyInferredData( declaringClass, property, propertyAccessor, @@ -1145,6 +1148,9 @@ public final class AnnotationBinder { annElts.add( propertyAnnotatedElement ); hasIdentifier = false; } + if ( element.isAnnotationPresent( MapsId.class ) ) { + mappings.addPropertyAnnotatedWithMapsId( entity, propertyAnnotatedElement ); + } return hasIdentifier; } @@ -1166,7 +1172,7 @@ public final class AnnotationBinder { * ordering does not matter */ Ejb3Column[] columns = null; - Ejb3JoinColumn[] joinColumns = null; + log.debug( "Processing annotations of {}.{}", propertyHolder.getEntityName(), inferredData.getPropertyName() ); @@ -1183,36 +1189,11 @@ public final class AnnotationBinder { } return; } + Ejb3JoinColumn[] joinColumns = buildExplicitJoinColumns( + propertyHolder, property, inferredData, entityBinder, mappings + ); - //process @JoinColumn(s) before @Column(s) to handle collection of entities properly - { - JoinColumn[] anns = null; - if ( property.isAnnotationPresent( JoinColumn.class ) ) { - anns = new JoinColumn[] { property.getAnnotation( JoinColumn.class ) }; - } - else if ( property.isAnnotationPresent( JoinColumns.class ) ) { - JoinColumns ann = property.getAnnotation( JoinColumns.class ); - anns = ann.value(); - int length = anns.length; - if ( length == 0 ) { - throw new AnnotationException( "Cannot bind an empty @JoinColumns" ); - } - } - if ( anns != null ) { - joinColumns = Ejb3JoinColumn.buildJoinColumns( - anns, null, entityBinder.getSecondaryTables(), - propertyHolder, inferredData.getPropertyName(), mappings - ); - } - else if ( property.isAnnotationPresent( JoinColumnsOrFormulas.class ) ) { - JoinColumnsOrFormulas ann = property.getAnnotation( JoinColumnsOrFormulas.class ); - joinColumns = Ejb3JoinColumn.buildJoinColumnsOrFormulas( - ann, null, entityBinder.getSecondaryTables(), - propertyHolder, inferredData.getPropertyName(), mappings - ); - } - } if ( property.isAnnotationPresent( Column.class ) || property.isAnnotationPresent( Formula.class ) ) { Column ann = property.getAnnotation( Column.class ); Formula formulaAnn = property.getAnnotation( Formula.class ); @@ -1234,30 +1215,9 @@ public final class AnnotationBinder { ( property.isAnnotationPresent( ManyToOne.class ) || property.isAnnotationPresent( OneToOne.class ) ) ) { - JoinTable joinTableAnn = propertyHolder.getJoinTable( property ); - if ( joinTableAnn != null ) { - joinColumns = Ejb3JoinColumn.buildJoinColumns( - joinTableAnn.inverseJoinColumns(), null, entityBinder.getSecondaryTables(), - propertyHolder, inferredData.getPropertyName(), mappings - ); - if ( StringHelper.isEmpty( joinTableAnn.name() ) ) { - throw new AnnotationException( - "JoinTable.name() on a @ToOne association has to be explicit: " - + StringHelper.qualify( propertyHolder.getPath(), inferredData.getPropertyName() ) - ); - } - } - else { - OneToOne oneToOneAnn = property.getAnnotation( OneToOne.class ); - String mappedBy = oneToOneAnn != null ? - oneToOneAnn.mappedBy() : - null; - joinColumns = Ejb3JoinColumn.buildJoinColumns( - (JoinColumn[]) null, - mappedBy, entityBinder.getSecondaryTables(), - propertyHolder, inferredData.getPropertyName(), mappings - ); - } + joinColumns = buildDefaultJoinColumnsForXToOne( + propertyHolder, property, inferredData, entityBinder, mappings + ); } else if ( joinColumns == null && ( property.isAnnotationPresent( OneToMany.class ) @@ -1843,6 +1803,69 @@ public final class AnnotationBinder { } } + private static Ejb3JoinColumn[] buildDefaultJoinColumnsForXToOne(PropertyHolder propertyHolder, XProperty property, PropertyData inferredData, EntityBinder entityBinder, ExtendedMappings mappings) { + Ejb3JoinColumn[] joinColumns; + JoinTable joinTableAnn = propertyHolder.getJoinTable( property ); + if ( joinTableAnn != null ) { + joinColumns = Ejb3JoinColumn.buildJoinColumns( + joinTableAnn.inverseJoinColumns(), null, entityBinder.getSecondaryTables(), + propertyHolder, inferredData.getPropertyName(), mappings + ); + if ( StringHelper.isEmpty( joinTableAnn.name() ) ) { + throw new AnnotationException( + "JoinTable.name() on a @ToOne association has to be explicit: " + + StringHelper.qualify( propertyHolder.getPath(), inferredData.getPropertyName() ) + ); + } + } + else { + OneToOne oneToOneAnn = property.getAnnotation( OneToOne.class ); + String mappedBy = oneToOneAnn != null ? + oneToOneAnn.mappedBy() : + null; + joinColumns = Ejb3JoinColumn.buildJoinColumns( + ( JoinColumn[]) null, + mappedBy, entityBinder.getSecondaryTables(), + propertyHolder, inferredData.getPropertyName(), mappings + ); + } + return joinColumns; + } + + private static Ejb3JoinColumn[] buildExplicitJoinColumns(PropertyHolder propertyHolder, XProperty property, PropertyData inferredData, EntityBinder entityBinder, ExtendedMappings mappings) { + //process @JoinColumn(s) before @Column(s) to handle collection of entities properly + Ejb3JoinColumn[] joinColumns = null; + { + JoinColumn[] anns = null; + + if ( property.isAnnotationPresent( JoinColumn.class ) ) { + anns = new JoinColumn[] { property.getAnnotation( JoinColumn.class ) }; + } + else if ( property.isAnnotationPresent( JoinColumns.class ) ) { + JoinColumns ann = property.getAnnotation( JoinColumns.class ); + anns = ann.value(); + int length = anns.length; + if ( length == 0 ) { + throw new AnnotationException( "Cannot bind an empty @JoinColumns" ); + } + } + if ( anns != null ) { + joinColumns = Ejb3JoinColumn.buildJoinColumns( + anns, null, entityBinder.getSecondaryTables(), + propertyHolder, inferredData.getPropertyName(), mappings + ); + } + else if ( property.isAnnotationPresent( JoinColumnsOrFormulas.class ) ) { + JoinColumnsOrFormulas ann = property.getAnnotation( JoinColumnsOrFormulas.class ); + joinColumns = Ejb3JoinColumn.buildJoinColumnsOrFormulas( + ann, null, entityBinder.getSecondaryTables(), + propertyHolder, inferredData.getPropertyName(), mappings + ); + } + } + return joinColumns; + } + //TODO move that to collection binder? private static void bindJoinedTableAssociation( XProperty property, ExtendedMappings mappings, EntityBinder entityBinder, @@ -1979,6 +2002,7 @@ public final class AnnotationBinder { inferredData, propertyHolder, mappings ); + final XClass entityXClass = inferredData.getPropertyClass(); List classElements = new ArrayList(); XClass returnedClassOrElement = inferredData.getClassOrElement(); @@ -1989,25 +2013,25 @@ public final class AnnotationBinder { baseClassElements = new ArrayList(); baseReturnedClassOrElement = baseInferredData.getClassOrElement(); bindTypeDefs(baseReturnedClassOrElement, mappings); - PropertyContainer propContainer = new PropertyContainer( baseReturnedClassOrElement ); + PropertyContainer propContainer = new PropertyContainer( baseReturnedClassOrElement, entityXClass ); addElementsOfClass( baseClassElements, propertyAccessor, propContainer, mappings ); } //embeddable elements can have type defs bindTypeDefs(returnedClassOrElement, mappings); - PropertyContainer propContainer = new PropertyContainer( returnedClassOrElement ); + PropertyContainer propContainer = new PropertyContainer( returnedClassOrElement, entityXClass ); addElementsOfClass( classElements, propertyAccessor, propContainer, mappings); //add elements of the embeddable superclass - XClass superClass = inferredData.getPropertyClass().getSuperclass(); + XClass superClass = entityXClass.getSuperclass(); while ( superClass != null && superClass.isAnnotationPresent( MappedSuperclass.class ) ) { //FIXME: proper support of typevariables incl var resolved at upper levels - propContainer = new PropertyContainer( superClass ); + propContainer = new PropertyContainer( superClass, entityXClass ); addElementsOfClass( classElements, propertyAccessor, propContainer, mappings ); superClass = superClass.getSuperclass(); } if ( baseClassElements != null ) { - if ( !hasIdClassAnnotations( inferredData.getPropertyClass() ) ) { + if ( !hasIdClassAnnotations( entityXClass ) ) { for ( int i = 0; i < classElements.size(); i++ ) { classElements.set( i, baseClassElements.get( i ) ); //this works since they are in the same order } @@ -2082,6 +2106,22 @@ public final class AnnotationBinder { setupComponentTuplizer( property, componentId ); } else { + final XClass persistentXClass; + try { + persistentXClass = mappings.getReflectionManager() + .classForName( persistentClassName, AnnotationBinder.class ); + } + catch ( ClassNotFoundException e ) { + throw new AssertionFailure( "Persistence class name cannot be converted into a Class", e); + } + final PropertyData annotatedWithMapsId = mappings.getPropertyAnnotatedWithMapsId( persistentXClass, "" ); + if ( annotatedWithMapsId != null ) { + columns = buildExplicitJoinColumns( propertyHolder, annotatedWithMapsId.getProperty(), annotatedWithMapsId, entityBinder, mappings ); + if (columns == null) { + columns = buildDefaultJoinColumnsForXToOne( propertyHolder, annotatedWithMapsId.getProperty(), annotatedWithMapsId, entityBinder, mappings ); + } + } + for (Ejb3Column column : columns) { column.forceNotNull(); //this is an id } diff --git a/annotations/src/main/java/org/hibernate/cfg/AnnotationConfiguration.java b/annotations/src/main/java/org/hibernate/cfg/AnnotationConfiguration.java index 4fb6e21c67..2b70b44fd7 100644 --- a/annotations/src/main/java/org/hibernate/cfg/AnnotationConfiguration.java +++ b/annotations/src/main/java/org/hibernate/cfg/AnnotationConfiguration.java @@ -46,6 +46,7 @@ import java.util.StringTokenizer; import javax.persistence.Embeddable; import javax.persistence.Entity; import javax.persistence.MappedSuperclass; +import javax.persistence.MapsId; import org.dom4j.Attribute; import org.dom4j.Document; @@ -64,6 +65,7 @@ import org.hibernate.DuplicateMappingException; import org.hibernate.HibernateException; import org.hibernate.Interceptor; import org.hibernate.MappingException; +import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.annotations.AnyMetaDef; import org.hibernate.annotations.common.reflection.MetadataProvider; @@ -141,6 +143,7 @@ public class AnnotationConfiguration extends Configuration { private transient ReflectionManager reflectionManager; private boolean isDefaultProcessed = false; private boolean isValidatorNotPresentLogged; + private Map> propertiesAnnotatedWithMapsId; public AnnotationConfiguration() { super(); @@ -279,6 +282,7 @@ public class AnnotationConfiguration extends Configuration { namingStrategy = EJB3NamingStrategy.INSTANCE; setEntityResolver( new EJB3DTDEntityResolver() ); anyMetaDefs = new HashMap(); + propertiesAnnotatedWithMapsId = new HashMap>(); reflectionManager = new JavaReflectionManager(); ( ( MetadataProviderInjector ) reflectionManager ).setMetadataProvider( new JPAMetadataProvider() ); @@ -1271,6 +1275,20 @@ public class AnnotationConfiguration extends Configuration { return inSecondPass; } + public PropertyData getPropertyAnnotatedWithMapsId(XClass entityType, String propertyName) { + final Map map = propertiesAnnotatedWithMapsId.get( entityType ); + return map == null ? null : map.get( propertyName ); + } + + public void addPropertyAnnotatedWithMapsId(XClass entityType, PropertyData property) { + Map map = propertiesAnnotatedWithMapsId.get( entityType ); + if (map == null) { + map = new HashMap(); + propertiesAnnotatedWithMapsId.put( entityType, map ); + } + map.put( property.getProperty().getAnnotation( MapsId.class ).value(), property ); + } + public IdGenerator getGenerator(String name) { return getGenerator( name, null ); } diff --git a/annotations/src/main/java/org/hibernate/cfg/ExtendedMappings.java b/annotations/src/main/java/org/hibernate/cfg/ExtendedMappings.java index 729e312dbb..37326e3d4d 100644 --- a/annotations/src/main/java/org/hibernate/cfg/ExtendedMappings.java +++ b/annotations/src/main/java/org/hibernate/cfg/ExtendedMappings.java @@ -171,4 +171,12 @@ public interface ExtendedMappings extends Mappings { public AnyMetaDef getAnyMetaDef(String name); public boolean isInSecondPass(); + + /** + * Return the property annotated with @MapsId("propertyName") if any. + * Null otherwise + */ + public PropertyData getPropertyAnnotatedWithMapsId(XClass entityType, String propertyName); + + public void addPropertyAnnotatedWithMapsId(XClass entityType, PropertyData property); } \ No newline at end of file diff --git a/annotations/src/main/java/org/hibernate/cfg/PropertyContainer.java b/annotations/src/main/java/org/hibernate/cfg/PropertyContainer.java index 8b622d2a2b..fd7564cb86 100644 --- a/annotations/src/main/java/org/hibernate/cfg/PropertyContainer.java +++ b/annotations/src/main/java/org/hibernate/cfg/PropertyContainer.java @@ -1,4 +1,4 @@ -// $Id:$ +// $Id$ /* * Hibernate, Relational Persistence for Idiomatic Java * @@ -55,20 +55,26 @@ import org.hibernate.util.StringHelper; class PropertyContainer { private static final Logger log = LoggerFactory.getLogger( AnnotationBinder.class ); + private final XClass entityAtStake; private final TreeMap fieldAccessMap; private final TreeMap propertyAccessMap; private final XClass xClass; private final AccessType explicitClassDefinedAccessType; - PropertyContainer(XClass clazz) { + PropertyContainer(XClass clazz, XClass entityAtStake) { this.xClass = clazz; + this.entityAtStake = entityAtStake; fieldAccessMap = initProperties( AccessType.FIELD ); propertyAccessMap = initProperties( AccessType.PROPERTY ); explicitClassDefinedAccessType = determineClassDefinedAccessStrategy(); checkForJpaAccess(); } - public XClass getXClass() { + public XClass getEntityAtStake() { + return entityAtStake; + } + + public XClass getDeclaringClass() { return xClass; } diff --git a/annotations/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/a/DerivedIdentitySimpleParentSimpleDepTest.java b/annotations/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/a/DerivedIdentitySimpleParentSimpleDepTest.java new file mode 100644 index 0000000000..68d2223ced --- /dev/null +++ b/annotations/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/a/DerivedIdentitySimpleParentSimpleDepTest.java @@ -0,0 +1,42 @@ +package org.hibernate.test.annotations.derivedidentities.e4.a; + +import org.hibernate.Session; +import org.hibernate.test.annotations.TestCase; +import org.hibernate.test.annotations.derivedidentities.e1.b.Dependent; +import org.hibernate.test.annotations.derivedidentities.e1.b.DependentId; +import org.hibernate.test.annotations.derivedidentities.e1.b.Employee; +import org.hibernate.test.util.SchemaUtil; + +/** + * @author Emmanuel Bernard + */ +public class DerivedIdentitySimpleParentSimpleDepTest extends TestCase { + + public void testIt() throws Exception { + assertTrue( SchemaUtil.isColumnPresent( "MedicalHistory", "FK", getCfg() ) ); + assertTrue( ! SchemaUtil.isColumnPresent( "MedicalHistory", "id", getCfg() ) ); + Person e = new Person(); + e.ssn = "aaa"; + Session s = openSession( ); + s.getTransaction().begin(); + s.persist( e ); + MedicalHistory d = new MedicalHistory(); + d.patient = e; + d.id = "aaa"; //FIXME not needed when foreign is enabled + s.persist( d ); + s.flush(); + s.clear(); + d = (MedicalHistory) s.get( MedicalHistory.class, d.id ); + assertEquals( d.id, d.patient.ssn ); + s.getTransaction().rollback(); + s.close(); + } + + @Override + protected Class[] getMappings() { + return new Class[] { + MedicalHistory.class, + Person.class + }; + } +} \ No newline at end of file diff --git a/annotations/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/a/MedicalHistory.java b/annotations/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/a/MedicalHistory.java new file mode 100644 index 0000000000..f4c008a85f --- /dev/null +++ b/annotations/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/a/MedicalHistory.java @@ -0,0 +1,20 @@ +package org.hibernate.test.annotations.derivedidentities.e4.a; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.MapsId; +import javax.persistence.OneToOne; + +/** + * @author Emmanuel Bernard + */ +@Entity +public class MedicalHistory { + @Id + String id; // overriding not allowed ... // default join column name is overridden @MapsId + @JoinColumn(name = "FK") + @MapsId + @OneToOne + Person patient; +} diff --git a/annotations/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/a/Person.java b/annotations/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/a/Person.java new file mode 100644 index 0000000000..3893bec700 --- /dev/null +++ b/annotations/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/a/Person.java @@ -0,0 +1,13 @@ +package org.hibernate.test.annotations.derivedidentities.e4.a; + +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * @author Emmanuel Bernard + */ +@Entity +public class Person { + @Id + String ssn; +} \ No newline at end of file