HHH-6114 Determining the default access type of a class hierarchy

This commit is contained in:
Hardy Ferentschik 2011-04-12 18:08:26 +02:00
parent 4d24c16b49
commit be7b44c2f7
5 changed files with 235 additions and 37 deletions

View File

@ -70,12 +70,12 @@ public class AnnotationBinder {
ClassInfo classInfo = entity.getClassInfo(); ClassInfo classInfo = entity.getClassInfo();
//@Entity and @MappedSuperclass on the same class leads to a NPE down the road //@Entity and @MappedSuperclass on the same class leads to a NPE down the road
AnnotationInstance jpaEntityAnnotation = JandexHelper.getSingleAnnotation( classInfo, ConfiguredClass.ENTITY ); AnnotationInstance jpaEntityAnnotation = JandexHelper.getSingleAnnotation( classInfo, JPADotNames.ENTITY );
AnnotationInstance mappedSuperClassAnnotation = JandexHelper.getSingleAnnotation( AnnotationInstance mappedSuperClassAnnotation = JandexHelper.getSingleAnnotation(
classInfo, ConfiguredClass.MAPPED_SUPER_CLASS classInfo, JPADotNames.MAPPED_SUPER_CLASS
); );
AnnotationInstance hibernateEntityAnnotation = JandexHelper.getSingleAnnotation( AnnotationInstance hibernateEntityAnnotation = JandexHelper.getSingleAnnotation(
classInfo, ConfiguredClass.HIBERNATE_ENTITY classInfo, JPADotNames.HIBERNATE_ENTITY
); );

View File

@ -38,10 +38,6 @@ import org.hibernate.AnnotationException;
* @author Hardy Ferentschik * @author Hardy Ferentschik
*/ */
public class ConfiguredClass { public class ConfiguredClass {
public static final DotName ENTITY = DotName.createSimple( Entity.class.getName() );
public static final DotName HIBERNATE_ENTITY = DotName.createSimple( org.hibernate.annotations.Entity.class.getName() );
public static final DotName MAPPED_SUPER_CLASS = DotName.createSimple( MappedSuperclass.class.getName() );
private final ClassInfo classInfo; private final ClassInfo classInfo;
private final ConfiguredClassHierarchy hierarchy; private final ConfiguredClassHierarchy hierarchy;
@ -53,9 +49,9 @@ public class ConfiguredClass {
private void init() { private void init() {
//@Entity and @MappedSuperclass on the same class leads to a NPE down the road //@Entity and @MappedSuperclass on the same class leads to a NPE down the road
AnnotationInstance jpaEntityAnnotation = JandexHelper.getSingleAnnotation( classInfo, ENTITY ); AnnotationInstance jpaEntityAnnotation = JandexHelper.getSingleAnnotation( classInfo, JPADotNames.ENTITY );
AnnotationInstance mappedSuperClassAnnotation = JandexHelper.getSingleAnnotation( classInfo, MAPPED_SUPER_CLASS ); AnnotationInstance mappedSuperClassAnnotation = JandexHelper.getSingleAnnotation( classInfo, JPADotNames.MAPPED_SUPER_CLASS );
AnnotationInstance hibernateEntityAnnotation = JandexHelper.getSingleAnnotation( classInfo, HIBERNATE_ENTITY ); AnnotationInstance hibernateEntityAnnotation = JandexHelper.getSingleAnnotation( classInfo, JPADotNames.HIBERNATE_ENTITY );
if ( jpaEntityAnnotation != null && mappedSuperClassAnnotation != null ) { if ( jpaEntityAnnotation != null && mappedSuperClassAnnotation != null ) {
throw new AnnotationException( throw new AnnotationException(

View File

@ -26,16 +26,24 @@ package org.hibernate.metamodel.source.annotations;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import javax.persistence.AccessType;
import javax.persistence.InheritanceType;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.ClassInfo; import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.FieldInfo;
import org.jboss.jandex.MethodInfo;
import org.hibernate.cfg.AccessType; import org.hibernate.AnnotationException;
/** /**
* Represents the inheritance structure of the configured classes within a class hierarchy.
*
* @author Hardy Ferentschik * @author Hardy Ferentschik
*/ */
public class ConfiguredClassHierarchy implements Iterable<ConfiguredClass> { public class ConfiguredClassHierarchy implements Iterable<ConfiguredClass> {
private final AccessType defaultAccessType; private final AccessType defaultAccessType;
private final InheritanceType inheritanceType;
private final List<ConfiguredClass> configuredClasses; private final List<ConfiguredClass> configuredClasses;
ConfiguredClassHierarchy(List<ClassInfo> classes) { ConfiguredClassHierarchy(List<ClassInfo> classes) {
@ -44,19 +52,28 @@ public class ConfiguredClassHierarchy implements Iterable<ConfiguredClass> {
configuredClasses.add( new ConfiguredClass( info, this ) ); configuredClasses.add( new ConfiguredClass( info, this ) );
} }
defaultAccessType = determineDefaultAccessType(); defaultAccessType = determineDefaultAccessType();
inheritanceType = determineInheritanceType();
} }
private AccessType determineDefaultAccessType() { public AccessType getDefaultAccessType() {
return null; return defaultAccessType;
} }
@Override public InheritanceType getInheritanceType() {
public Iterator<ConfiguredClass> iterator() { return inheritanceType;
}
/**
* @return An iterator iterating in top down manner over the configured classes in this hierarchy.
*/
public Iterator<ConfiguredClass> iterator
() {
return configuredClasses.iterator(); return configuredClasses.iterator();
} }
@Override @Override
public String toString() { public String toString
() {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
sb.append( "ConfiguredClassHierarchy" ); sb.append( "ConfiguredClassHierarchy" );
sb.append( "{defaultAccessType=" ).append( defaultAccessType ); sb.append( "{defaultAccessType=" ).append( defaultAccessType );
@ -64,6 +81,78 @@ public class ConfiguredClassHierarchy implements Iterable<ConfiguredClass> {
sb.append( '}' ); sb.append( '}' );
return sb.toString(); return sb.toString();
} }
/**
* @return Returns the default access type for the configured class hierarchy independent of explicit
* {@code AccessType} annotations. The default access type is determined by the placement of the
* annotations.
*/
private AccessType determineDefaultAccessType() {
Iterator<ConfiguredClass> iter = iterator();
AccessType accessType = null;
while ( iter.hasNext() ) {
ConfiguredClass configuredClass = iter.next();
ClassInfo info = configuredClass.getClassInfo();
List<AnnotationInstance> idAnnotations = info.annotations().get( JPADotNames.ID );
if ( idAnnotations == null || idAnnotations.size() == 0 ) {
continue;
}
for ( AnnotationInstance annotation : idAnnotations ) {
AccessType tmpAccessType;
if ( annotation.target() instanceof FieldInfo ) {
tmpAccessType = AccessType.FIELD;
}
else if ( annotation.target() instanceof MethodInfo ) {
tmpAccessType = AccessType.PROPERTY;
}
else {
throw new AnnotationException( "Invalid placement of @Id annotation" );
}
if ( accessType == null ) {
accessType = tmpAccessType;
}
else {
if ( !accessType.equals( tmpAccessType ) ) {
throw new AnnotationException( "Inconsistent placement of @Id annotation within hierarchy " + hierarchyListString() );
}
}
}
}
if ( accessType == null ) {
return throwIdNotFoundAnnotationException();
}
return accessType;
}
private InheritanceType determineInheritanceType() {
return null; //To change body of created methods use File | Settings | File Templates.
}
private AccessType throwIdNotFoundAnnotationException() {
StringBuilder builder = new StringBuilder();
builder.append( "Unable to find Id property for class hierarchy " );
builder.append( hierarchyListString() );
throw new AnnotationException( builder.toString() );
}
private String hierarchyListString() {
Iterator<ConfiguredClass> iter;
StringBuilder builder = new StringBuilder();
builder.append( "[" );
iter = iterator();
while ( iter.hasNext() ) {
builder.append( iter.next().getClassInfo().name().toString() );
if ( iter.hasNext() ) {
builder.append( ", " );
}
}
builder.append( "]" );
return builder.toString();
}
} }

View File

@ -0,0 +1,46 @@
/*
* 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 javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import org.jboss.jandex.DotName;
/**
* Defines the dot names for the JPA annotations
*
* @author Hardy Ferentschik
*/
public interface JPADotNames {
public static final DotName ENTITY = DotName.createSimple( Entity.class.getName() );
public static final DotName HIBERNATE_ENTITY = DotName.createSimple( org.hibernate.annotations.Entity.class.getName() );
public static final DotName MAPPED_SUPER_CLASS = DotName.createSimple( MappedSuperclass.class.getName() );
public static final DotName ID = DotName.createSimple( Id.class.getName() );
}

View File

@ -4,6 +4,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Iterator; import java.util.Iterator;
import java.util.Set; import java.util.Set;
import javax.persistence.AccessType;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
import javax.persistence.Id; import javax.persistence.Id;
@ -19,6 +20,7 @@ import org.hibernate.AnnotationException;
import org.hibernate.testing.junit4.BaseUnitTestCase; import org.hibernate.testing.junit4.BaseUnitTestCase;
import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
@ -65,6 +67,22 @@ public class ConfiguredClassHierarchyBuilderTest extends BaseUnitTestCase {
@Test @Test
public void testMappedSuperClass() { public void testMappedSuperClass() {
@MappedSuperclass
class MappedSuperClass {
@Id
@GeneratedValue
private int id;
}
class UnmappedSubClass extends MappedSuperClass {
private String unmappedProperty;
}
@Entity
class MappedSubClass extends UnmappedSubClass {
private String mappedProperty;
}
Index index = indexForClass( MappedSubClass.class, MappedSuperClass.class, UnmappedSubClass.class ); Index index = indexForClass( MappedSubClass.class, MappedSuperClass.class, UnmappedSubClass.class );
ConfiguredClassHierarchyBuilder builder = new ConfiguredClassHierarchyBuilder(); ConfiguredClassHierarchyBuilder builder = new ConfiguredClassHierarchyBuilder();
Set<ConfiguredClassHierarchy> hierarchies = builder.createEntityHierarchies( index ); Set<ConfiguredClassHierarchy> hierarchies = builder.createEntityHierarchies( index );
@ -82,11 +100,81 @@ public class ConfiguredClassHierarchyBuilderTest extends BaseUnitTestCase {
@Test(expected = AnnotationException.class) @Test(expected = AnnotationException.class)
public void testEntityAndMappedSuperClassAnnotations() { public void testEntityAndMappedSuperClassAnnotations() {
@Entity
@MappedSuperclass
class EntityAndMappedSuperClass {
}
Index index = indexForClass( EntityAndMappedSuperClass.class ); Index index = indexForClass( EntityAndMappedSuperClass.class );
ConfiguredClassHierarchyBuilder builder = new ConfiguredClassHierarchyBuilder(); ConfiguredClassHierarchyBuilder builder = new ConfiguredClassHierarchyBuilder();
builder.createEntityHierarchies( index ); builder.createEntityHierarchies( index );
} }
@Test(expected = AnnotationException.class)
public void testNoIdAnnotation() {
@Entity
class A {
String id;
}
@Entity
class B extends A {
}
Index index = indexForClass( B.class, A.class );
ConfiguredClassHierarchyBuilder builder = new ConfiguredClassHierarchyBuilder();
builder.createEntityHierarchies( index );
}
@Test
public void testDefaultFieldAccess() {
@Entity
class A {
@Id
String id;
}
@Entity
class B extends A {
}
Index index = indexForClass( B.class, A.class );
ConfiguredClassHierarchyBuilder builder = new ConfiguredClassHierarchyBuilder();
Set<ConfiguredClassHierarchy> hierarchies = builder.createEntityHierarchies( index );
assertTrue( hierarchies.size() == 1 );
ConfiguredClassHierarchy hierarchy = hierarchies.iterator().next();
assertEquals( "Wrong default access type", AccessType.FIELD, hierarchy.getDefaultAccessType() );
}
@Test
public void testDefaultPropertyAccess() {
@Entity
class A {
String id;
@Id
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
@Entity
class B extends A {
}
Index index = indexForClass( B.class, A.class );
ConfiguredClassHierarchyBuilder builder = new ConfiguredClassHierarchyBuilder();
Set<ConfiguredClassHierarchy> hierarchies = builder.createEntityHierarchies( index );
assertTrue( hierarchies.size() == 1 );
ConfiguredClassHierarchy hierarchy = hierarchies.iterator().next();
assertEquals( "Wrong default access type", AccessType.PROPERTY, hierarchy.getDefaultAccessType() );
}
private Index indexForClass(Class<?>... classes) { private Index indexForClass(Class<?>... classes) {
Indexer indexer = new Indexer(); Indexer indexer = new Indexer();
for ( Class<?> clazz : classes ) { for ( Class<?> clazz : classes ) {
@ -121,27 +209,6 @@ public class ConfiguredClassHierarchyBuilderTest extends BaseUnitTestCase {
public class B extends A { public class B extends A {
private String name; private String name;
} }
@MappedSuperclass
public class MappedSuperClass {
@Id
@GeneratedValue
private int id;
}
public class UnmappedSubClass extends MappedSuperClass {
private String unmappedProperty;
}
@Entity
public class MappedSubClass extends UnmappedSubClass {
private String mappedProperty;
}
@Entity
@MappedSuperclass
public class EntityAndMappedSuperClass {
}
} }