package org.hibernate.cfg;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.persistence.Access;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Transient;
import org.hibernate.AnnotationException;
import org.hibernate.MappingException;
import org.hibernate.annotations.ManyToAny;
import org.hibernate.annotations.Target;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper;
import org.jboss.logging.Logger;
class PropertyContainer {
static {
System.setProperty("jboss.i18n.generate-proxies", "true");
}
private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, PropertyContainer.class.getName());
private final AccessType explicitClassDefinedAccessType;
private final TreeMap<String, XProperty> fieldAccessMap;
private final TreeMap<String, XProperty> propertyAccessMap;
private final XClass xClass;
private final XClass entityAtStake;
PropertyContainer(XClass clazz, XClass entityAtStake) {
this.xClass = clazz;
this.entityAtStake = entityAtStake;
explicitClassDefinedAccessType = determineClassDefinedAccessStrategy();
fieldAccessMap = initProperties( AccessType.FIELD );
propertyAccessMap = initProperties( AccessType.PROPERTY );
considerExplicitFieldAndPropertyAccess();
}
public XClass getEntityAtStake() {
return entityAtStake;
}
public XClass getDeclaringClass() {
return xClass;
}
public AccessType getExplicitAccessStrategy() {
return explicitClassDefinedAccessType;
}
public boolean hasExplicitAccessStrategy() {
return !explicitClassDefinedAccessType.equals( AccessType.DEFAULT );
}
public Collection<XProperty> getProperties(AccessType accessType) {
assertTypesAreResolvable( accessType );
if ( AccessType.DEFAULT == accessType || AccessType.PROPERTY == accessType ) {
return Collections.unmodifiableCollection( propertyAccessMap.values() );
}
else {
return Collections.unmodifiableCollection( fieldAccessMap.values() );
}
}
private void assertTypesAreResolvable(AccessType access) {
Map<String, XProperty> xprops;
if ( AccessType.PROPERTY.equals( access ) || AccessType.DEFAULT.equals( access ) ) {
xprops = propertyAccessMap;
}
else {
xprops = fieldAccessMap;
}
for ( XProperty property : xprops.values() ) {
if ( !property.isTypeResolved() && !discoverTypeWithoutReflection( property ) ) {
String msg = "Property " + StringHelper.qualify( xClass.getName(), property.getName() ) +
" has an unbound type and no explicit target entity. Resolve this Generic usage issue" +
" or set an explicit target attribute (eg @OneToMany(target=) or use an explicit @Type";
throw new AnnotationException( msg );
}
}
}
private void considerExplicitFieldAndPropertyAccess() {
for ( XProperty property : fieldAccessMap.values() ) {
Access access = property.getAnnotation( Access.class );
if ( access == null ) {
continue;
}
AccessType accessType = AccessType.getAccessStrategy( access.value() );
if (accessType == AccessType.FIELD) {
propertyAccessMap.put(property.getName(), property);
}
else {
LOG.debug( "Placing @Access(AccessType.FIELD) on a field does not have any effect." );
}
}
for ( XProperty property : propertyAccessMap.values() ) {
Access access = property.getAnnotation( Access.class );
if ( access == null ) {
continue;
}
AccessType accessType = AccessType.getAccessStrategy( access.value() );
if (accessType == AccessType.PROPERTY) {
fieldAccessMap.put(property.getName(), property);
}
else {
LOG.debug( "Placing @Access(AccessType.PROPERTY) on a field does not have any effect." );
}
}
}
private TreeMap<String, XProperty> initProperties(AccessType access) {
if ( !( AccessType.PROPERTY.equals( access ) || AccessType.FIELD.equals( access ) ) ) {
throw new IllegalArgumentException( "Access type has to be AccessType.FIELD or AccessType.Property" );
}
TreeMap<String, XProperty> propertiesMap = new TreeMap<String, XProperty>();
List<XProperty> properties = xClass.getDeclaredProperties( access.getType() );
for ( XProperty property : properties ) {
if ( mustBeSkipped( property ) ) {
continue;
}
propertiesMap.put( property.getName(), property );
}
return propertiesMap;
}
private AccessType determineClassDefinedAccessStrategy() {
AccessType classDefinedAccessType;
AccessType hibernateDefinedAccessType = AccessType.DEFAULT;
AccessType jpaDefinedAccessType = AccessType.DEFAULT;
org.hibernate.annotations.AccessType accessType = xClass.getAnnotation( org.hibernate.annotations.AccessType.class );
if ( accessType != null ) {
hibernateDefinedAccessType = AccessType.getAccessStrategy( accessType.value() );
}
Access access = xClass.getAnnotation( Access.class );
if ( access != null ) {
jpaDefinedAccessType = AccessType.getAccessStrategy( access.value() );
}
if ( hibernateDefinedAccessType != AccessType.DEFAULT
&& jpaDefinedAccessType != AccessType.DEFAULT
&& hibernateDefinedAccessType != jpaDefinedAccessType ) {
throw new MappingException(
"@AccessType and @Access specified with contradicting values. Use of @Access only is recommended. "
);
}
if ( hibernateDefinedAccessType != AccessType.DEFAULT ) {
classDefinedAccessType = hibernateDefinedAccessType;
}
else {
classDefinedAccessType = jpaDefinedAccessType;
}
return classDefinedAccessType;
}
private static boolean discoverTypeWithoutReflection(XProperty p) {
if ( p.isAnnotationPresent( OneToOne.class ) && !p.getAnnotation( OneToOne.class )
.targetEntity()
.equals( void.class ) ) {
return true;
}
else if ( p.isAnnotationPresent( OneToMany.class ) && !p.getAnnotation( OneToMany.class )
.targetEntity()
.equals( void.class ) ) {
return true;
}
else if ( p.isAnnotationPresent( ManyToOne.class ) && !p.getAnnotation( ManyToOne.class )
.targetEntity()
.equals( void.class ) ) {
return true;
}
else if ( p.isAnnotationPresent( ManyToMany.class ) && !p.getAnnotation( ManyToMany.class )
.targetEntity()
.equals( void.class ) ) {
return true;
}
else if ( p.isAnnotationPresent( org.hibernate.annotations.Any.class ) ) {
return true;
}
else if ( p.isAnnotationPresent( ManyToAny.class ) ) {
if ( !p.isCollection() && !p.isArray() ) {
throw new AnnotationException( "@ManyToAny used on a non collection non array property: " + p.getName() );
}
return true;
}
else if ( p.isAnnotationPresent( Type.class ) ) {
return true;
}
else if ( p.isAnnotationPresent( Target.class ) ) {
return true;
}
return false;
}
private static boolean mustBeSkipped(XProperty property) {
return property.isAnnotationPresent( Transient.class )
|| "net.sf.cglib.transform.impl.InterceptFieldCallback".equals( property.getType().getName() )
|| "org.hibernate.bytecode.internal.javassist.FieldHandler".equals( property.getType().getName() );
}
}