HHH-6174 extracting table name from hierarchy

This commit is contained in:
Hardy Ferentschik 2011-04-29 20:47:28 +02:00
parent 627b4ea6a1
commit 1ae1d4b699
9 changed files with 406 additions and 26 deletions

View File

@ -35,17 +35,18 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.AccessType;
import javax.persistence.InheritanceType;
import com.fasterxml.classmate.ResolvedTypeWithMembers;
import com.fasterxml.classmate.members.HierarchicType;
import com.fasterxml.classmate.members.ResolvedMember;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.FieldInfo;
import org.jboss.jandex.MethodInfo;
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
import org.hibernate.metamodel.source.annotations.util.JandexHelper;
@ -59,27 +60,61 @@ import org.hibernate.service.classloading.spi.ClassLoaderService;
* @author Hardy Ferentschik
*/
public class ConfiguredClass {
/**
* The parent of this configured class or {@code null} in case this configured class is the root of a hierarchy.
*/
private final ConfiguredClass parent;
private final ClassInfo classInfo;
private final Class<?> clazz;
private final boolean isRoot;
private final AccessType classAccessType;
private final AccessType hierarchyAccessType;
private final InheritanceType inheritanceType;
private final boolean hasOwnTable;
private final String primaryTableName;
private final AnnotationInstance tableAnnotation;
private final boolean isMappedSuperClass;
private final boolean isEmbeddable;
private final Map<String, MappedProperty> mappedProperties;
public ConfiguredClass(ClassInfo info, ConfiguredClass parent, AccessType hierarchyAccessType, ServiceRegistry serviceRegistry, ResolvedTypeWithMembers resolvedType) {
public ConfiguredClass(ClassInfo info,
ConfiguredClass parent,
AccessType hierarchyAccessType,
InheritanceType inheritanceType,
ServiceRegistry serviceRegistry,
ResolvedTypeWithMembers resolvedType) {
this.classInfo = info;
this.parent = parent;
this.isRoot = parent == null;
this.hierarchyAccessType = hierarchyAccessType;
AnnotationInstance mappedSuperClassAnnotation = assertNotEntityAndMappedSuperClass();
this.inheritanceType = inheritanceType;
this.clazz = serviceRegistry.getService( ClassLoaderService.class ).classForName( info.toString() );
AnnotationInstance mappedSuperClassAnnotation = JandexHelper.getSingleAnnotation(
classInfo, JPADotNames.MAPPED_SUPER_CLASS
);
isMappedSuperClass = mappedSuperClassAnnotation != null;
AnnotationInstance embeddableAnnotation = JandexHelper.getSingleAnnotation(
classInfo, JPADotNames.MAPPED_SUPER_CLASS
);
isEmbeddable = embeddableAnnotation != null;
tableAnnotation = JandexHelper.getSingleAnnotation(
classInfo, JPADotNames.TABLE
);
// todo think about how exactly to handle embeddables regarding access type etc
classAccessType = determineClassAccessType();
hasOwnTable = definesItsOwnTable();
primaryTableName = determinePrimaryTableName();
List<MappedProperty> properties = collectMappedProperties( resolvedType );
// make sure the properties are ordered by property name
Collections.sort( properties );
@ -110,6 +145,22 @@ public class ConfiguredClass {
return isMappedSuperClass;
}
public boolean isEmbeddable() {
return isEmbeddable;
}
public InheritanceType getInheritanceType() {
return inheritanceType;
}
public boolean hasOwnTable() {
return hasOwnTable;
}
public String getPrimaryTableName() {
return primaryTableName;
}
public Iterable<MappedProperty> getMappedProperties() {
return mappedProperties.values();
}
@ -126,6 +177,7 @@ public class ConfiguredClass {
sb.append( ", mappedProperties=" ).append( mappedProperties );
sb.append( ", classAccessType=" ).append( classAccessType );
sb.append( ", isRoot=" ).append( isRoot );
sb.append( ", inheritanceType=" ).append( inheritanceType );
sb.append( '}' );
return sb.toString();
}
@ -344,20 +396,39 @@ public class ConfiguredClass {
}
}
private AnnotationInstance assertNotEntityAndMappedSuperClass() {
//@Entity and @MappedSuperclass on the same class leads to a NPE down the road
AnnotationInstance jpaEntityAnnotation = JandexHelper.getSingleAnnotation( classInfo, JPADotNames.ENTITY );
AnnotationInstance mappedSuperClassAnnotation = JandexHelper.getSingleAnnotation(
classInfo, JPADotNames.MAPPED_SUPER_CLASS
);
if ( jpaEntityAnnotation != null && mappedSuperClassAnnotation != null ) {
throw new AnnotationException(
"An entity cannot be annotated with both @Entity and @MappedSuperclass: "
+ classInfo.name().toString()
);
private boolean definesItsOwnTable() {
// mapped super classes and embeddables don't have their own tables
if ( isMappedSuperClass() || isEmbeddable() ) {
return false;
}
return mappedSuperClassAnnotation;
if ( InheritanceType.SINGLE_TABLE.equals( inheritanceType ) ) {
if ( isRoot() ) {
return true;
}
else {
return false;
}
}
return true;
}
private String determinePrimaryTableName() {
String tableName = null;
if ( hasOwnTable() ) {
tableName = clazz.getSimpleName();
if ( tableAnnotation != null ) {
AnnotationValue value = tableAnnotation.value( "name" );
String tmp = value == null ? null : value.asString();
if ( tmp != null && !tmp.isEmpty() ) {
tableName = tmp;
}
}
}
else if ( parent != null && !parent.isMappedSuperClass && !parent.isEmbeddable ) {
tableName = parent.getPrimaryTableName();
}
return tableName;
}
}

View File

@ -66,7 +66,9 @@ public class ConfiguredClassHierarchy implements Iterable<ConfiguredClass> {
configuredClasses = new ArrayList<ConfiguredClass>();
ConfiguredClass parent = null;
for ( ClassInfo info : classes ) {
ConfiguredClass configuredClass = new ConfiguredClass( info, parent, defaultAccessType, serviceRegistry, resolvedMembers );
ConfiguredClass configuredClass = new ConfiguredClass(
info, parent, defaultAccessType, inheritanceType, serviceRegistry, resolvedMembers
);
configuredClasses.add( configuredClass );
parent = configuredClass;
}

View File

@ -70,6 +70,17 @@ public class EntityBinder {
}
private void bindTable(EntityBinding entityBinding) {
// this.schemaName = new Schema.Name(
// ( entityClazz.getSchema() == null ?
// hibernateMappingBinder.getDefaultSchemaName() :
// entityClazz.getSchema() ),
// ( entityClazz.getCatalog() == null ?
// hibernateMappingBinder.getDefaultCatalogName() :
// entityClazz.getCatalog() )
// );
final Schema schema = meta.getDatabase().getSchema( null );
}

View File

@ -29,6 +29,7 @@ import org.hibernate.annotations.Check;
import org.hibernate.annotations.Entity;
import org.hibernate.annotations.FetchProfile;
import org.hibernate.annotations.FetchProfiles;
import org.hibernate.annotations.Table;
/**
* Defines the dot names for the Hibernate specific annotations.
@ -37,6 +38,8 @@ import org.hibernate.annotations.FetchProfiles;
*/
public interface HibernateDotNames {
public static final DotName ENTITY = DotName.createSimple( Entity.class.getName() );
public static final DotName TABLE = DotName.createSimple( Table.class.getName() );
public static final DotName FETCH_PROFILES = DotName.createSimple( FetchProfiles.class.getName() );
public static final DotName FETCH_PROFILE = DotName.createSimple( FetchProfile.class.getName() );

View File

@ -24,6 +24,7 @@
package org.hibernate.metamodel.source.annotations;
import javax.persistence.Access;
import javax.persistence.Embeddable;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Id;
@ -42,6 +43,8 @@ import org.jboss.jandex.DotName;
public interface JPADotNames {
public static final DotName ENTITY = DotName.createSimple( Entity.class.getName() );
public static final DotName MAPPED_SUPER_CLASS = DotName.createSimple( MappedSuperclass.class.getName() );
public static final DotName EMBEDDABLE = DotName.createSimple( Embeddable.class.getName() );
public static final DotName INHERITANCE = DotName.createSimple( Inheritance.class.getName() );
public static final DotName ID = DotName.createSimple( Id.class.getName() );

View File

@ -0,0 +1,28 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, 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.metamodel.source.annotations;
/**
* This package contains the core binding code for binding annotation based configuration to the Hibernate metadata model.
*/

View File

@ -35,6 +35,7 @@ import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.Index;
import org.hibernate.AnnotationException;
import org.hibernate.metamodel.source.annotations.ConfiguredClassHierarchy;
import org.hibernate.metamodel.source.annotations.JPADotNames;
import org.hibernate.service.ServiceRegistry;
@ -54,19 +55,14 @@ public class ConfiguredClassHierarchyBuilder {
* @param index The annotation index
* @param serviceRegistry The service registry
*
* @return a set of {@code ConfiguredClassHierarchy}s. One for each configured "leaf" entity.
* @return a set of {@code ConfiguredClassHierarchy}s. One for each "leaf" entity.
*/
public static Set<ConfiguredClassHierarchy> createEntityHierarchies(Index index, ServiceRegistry serviceRegistry) {
ClassLoaderService classLoaderService = serviceRegistry.getService( ClassLoaderService.class );
Map<ClassInfo, List<ClassInfo>> processedClassInfos = new HashMap<ClassInfo, List<ClassInfo>>();
for ( ClassInfo info : index.getKnownClasses() ) {
AnnotationInstance jpaEntityAnnotation = JandexHelper.getSingleAnnotation( info, JPADotNames.ENTITY );
AnnotationInstance mappedSuperClassAnnotation = JandexHelper.getSingleAnnotation(
info, JPADotNames.MAPPED_SUPER_CLASS
);
// we are only interested in building the class hierarchies for @Entity or @MappedSuperclass w
if ( jpaEntityAnnotation == null && mappedSuperClassAnnotation == null ) {
if ( !isConfiguredClass( info ) ) {
continue;
}
@ -110,6 +106,29 @@ public class ConfiguredClassHierarchyBuilder {
return hierarchies;
}
private static boolean isConfiguredClass(ClassInfo info) {
boolean isConfiguredClass = true;
AnnotationInstance jpaEntityAnnotation = JandexHelper.getSingleAnnotation( info, JPADotNames.ENTITY );
AnnotationInstance mappedSuperClassAnnotation = JandexHelper.getSingleAnnotation(
info, JPADotNames.MAPPED_SUPER_CLASS
);
AnnotationInstance embeddableAnnotation = JandexHelper.getSingleAnnotation(
info, JPADotNames.EMBEDDABLE
);
// we are only interested in building the class hierarchies for @Entity or @MappedSuperclass w
if ( jpaEntityAnnotation == null && mappedSuperClassAnnotation == null && embeddableAnnotation == null ) {
return false;
}
// some sanity checks
String className = info.toString();
assertNotEntityAndMappedSuperClass( jpaEntityAnnotation, mappedSuperClassAnnotation, className );
assertNotEntityAndEmbeddable( jpaEntityAnnotation, embeddableAnnotation, className );
return isConfiguredClass;
}
private static boolean existsHierarchyWithClassInfoAsLeaf(Map<ClassInfo, List<ClassInfo>> processedClassInfos, ClassInfo tmpClassInfo) {
if ( !processedClassInfos.containsKey( tmpClassInfo ) ) {
return false;
@ -118,6 +137,22 @@ public class ConfiguredClassHierarchyBuilder {
List<ClassInfo> classInfoList = processedClassInfos.get( tmpClassInfo );
return classInfoList.get( classInfoList.size() - 1 ).equals( tmpClassInfo );
}
private static void assertNotEntityAndMappedSuperClass(AnnotationInstance jpaEntityAnnotation, AnnotationInstance mappedSuperClassAnnotation, String className) {
if ( jpaEntityAnnotation != null && mappedSuperClassAnnotation != null ) {
throw new AnnotationException(
"An entity cannot be annotated with both @Entity and @MappedSuperclass. " + className + " has both annotations."
);
}
}
private static void assertNotEntityAndEmbeddable(AnnotationInstance jpaEntityAnnotation, AnnotationInstance embeddableAnnotation, String className) {
if ( jpaEntityAnnotation != null && embeddableAnnotation != null ) {
throw new AnnotationException(
"An entity cannot be annotated with both @Entity and @Embeddable. " + className + " has both annotations."
);
}
}
}

View File

@ -0,0 +1,215 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, 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.metamodel.source.annotations;
import java.util.Iterator;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.Index;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.hibernate.metamodel.source.annotations.util.ConfiguredClassHierarchyBuilder;
import org.hibernate.metamodel.source.annotations.util.JandexHelper;
import org.hibernate.service.ServiceRegistryBuilder;
import org.hibernate.service.classloading.spi.ClassLoaderService;
import org.hibernate.service.internal.BasicServiceRegistryImpl;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
/**
* @author Hardy Ferentschik
*/
public class TableNameTest extends BaseUnitTestCase {
private BasicServiceRegistryImpl serviceRegistry;
private ClassLoaderService service;
@Before
public void setUp() {
serviceRegistry = (BasicServiceRegistryImpl) new ServiceRegistryBuilder().buildServiceRegistry();
service = serviceRegistry.getService( ClassLoaderService.class );
}
@After
public void tearDown() {
serviceRegistry.destroy();
}
@Test
public void testSingleInheritanceDefaultTableName() {
@Entity
class A {
@Id
@GeneratedValue
private int id;
}
@Entity
class B extends A {
}
Index index = JandexHelper.indexForClass( service, A.class, B.class );
Set<ConfiguredClassHierarchy> hierarchies = ConfiguredClassHierarchyBuilder.createEntityHierarchies(
index, serviceRegistry
);
assertEquals( "There should be only one hierarchy", 1, hierarchies.size() );
Iterator<ConfiguredClass> iter = hierarchies.iterator().next().iterator();
ConfiguredClass configuredClass = iter.next();
ClassInfo info = configuredClass.getClassInfo();
assertEquals( "wrong class", DotName.createSimple( A.class.getName() ), info.name() );
assertTrue( configuredClass.hasOwnTable() );
Assert.assertEquals(
"wrong inheritance type", InheritanceType.SINGLE_TABLE, configuredClass.getInheritanceType()
);
Assert.assertEquals(
"wrong table name", "A", configuredClass.getPrimaryTableName()
);
assertTrue( iter.hasNext() );
configuredClass = iter.next();
info = configuredClass.getClassInfo();
assertEquals( "wrong class", DotName.createSimple( B.class.getName() ), info.name() );
assertFalse( configuredClass.hasOwnTable() );
Assert.assertEquals(
"wrong inheritance type", InheritanceType.SINGLE_TABLE, configuredClass.getInheritanceType()
);
Assert.assertEquals(
"wrong table name", "A", configuredClass.getPrimaryTableName()
);
assertFalse( iter.hasNext() );
}
@Test
public void testTablePerClassDefaultTableName() {
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
class A {
@Id
@GeneratedValue
private int id;
}
@Entity
class B extends A {
}
Index index = JandexHelper.indexForClass( service, A.class, B.class );
Set<ConfiguredClassHierarchy> hierarchies = ConfiguredClassHierarchyBuilder.createEntityHierarchies(
index, serviceRegistry
);
assertEquals( "There should be only one hierarchy", 1, hierarchies.size() );
Iterator<ConfiguredClass> iter = hierarchies.iterator().next().iterator();
ConfiguredClass configuredClass = iter.next();
ClassInfo info = configuredClass.getClassInfo();
assertEquals( "wrong class", DotName.createSimple( A.class.getName() ), info.name() );
assertTrue( configuredClass.hasOwnTable() );
Assert.assertEquals(
"wrong inheritance type", InheritanceType.TABLE_PER_CLASS, configuredClass.getInheritanceType()
);
Assert.assertEquals(
"wrong table name", "A", configuredClass.getPrimaryTableName()
);
assertTrue( iter.hasNext() );
configuredClass = iter.next();
info = configuredClass.getClassInfo();
assertEquals( "wrong class", DotName.createSimple( B.class.getName() ), info.name() );
assertTrue( configuredClass.hasOwnTable() );
Assert.assertEquals(
"wrong inheritance type", InheritanceType.TABLE_PER_CLASS, configuredClass.getInheritanceType()
);
Assert.assertEquals(
"wrong table name", "B", configuredClass.getPrimaryTableName()
);
assertFalse( iter.hasNext() );
}
@Test
public void testJoinedSubclassDefaultTableName() {
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@Table(name = "FOO")
class A {
@Id
@GeneratedValue
private int id;
}
@Entity
class B extends A {
}
Index index = JandexHelper.indexForClass( service, B.class, A.class );
Set<ConfiguredClassHierarchy> hierarchies = ConfiguredClassHierarchyBuilder.createEntityHierarchies(
index, serviceRegistry
);
assertEquals( "There should be only one hierarchy", 1, hierarchies.size() );
Iterator<ConfiguredClass> iter = hierarchies.iterator().next().iterator();
ConfiguredClass configuredClass = iter.next();
ClassInfo info = configuredClass.getClassInfo();
assertEquals( "wrong class", DotName.createSimple( A.class.getName() ), info.name() );
assertTrue( configuredClass.hasOwnTable() );
Assert.assertEquals(
"wrong inheritance type", InheritanceType.JOINED, configuredClass.getInheritanceType()
);
Assert.assertEquals(
"wrong table name", "FOO", configuredClass.getPrimaryTableName()
);
assertTrue( iter.hasNext() );
configuredClass = iter.next();
info = configuredClass.getClassInfo();
assertEquals( "wrong class", DotName.createSimple( B.class.getName() ), info.name() );
assertTrue( configuredClass.hasOwnTable() );
Assert.assertEquals(
"wrong inheritance type", InheritanceType.JOINED, configuredClass.getInheritanceType()
);
Assert.assertEquals(
"wrong table name", "B", configuredClass.getPrimaryTableName()
);
assertFalse( iter.hasNext() );
}
}

View File

@ -26,6 +26,7 @@ package org.hibernate.metamodel.source.annotations.util;
import java.util.Iterator;
import java.util.Set;
import javax.persistence.AccessType;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@ -157,6 +158,17 @@ public class ConfiguredClassHierarchyBuilderTest extends BaseUnitTestCase {
ConfiguredClassHierarchyBuilder.createEntityHierarchies( index, serviceRegistry );
}
@Test(expected = AnnotationException.class)
public void testEntityAndEmbeddableAnnotations() {
@Entity
@Embeddable
class EntityAndEmbeddable {
}
Index index = JandexHelper.indexForClass( service, EntityAndEmbeddable.class );
ConfiguredClassHierarchyBuilder.createEntityHierarchies( index, serviceRegistry );
}
@Test(expected = AnnotationException.class)
public void testNoIdAnnotation() {