/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 2010, 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
 */

// $Id$

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;

A helper class to keep the XPropertys of a class ordered by access type.
Author:Hardy Ferentschik
/** * A helper class to keep the {@code XProperty}s of a class ordered by access type. * * @author Hardy Ferentschik */
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;
Constains the properties which must be returned in case the class is accessed via AccessType.FIELD. Note, this does not mean that all XPropertys in this map are fields. Due to JPA access rules single properties can have different access type than the overall class access type.
/** * Constains the properties which must be returned in case the class is accessed via {@code AccessType.FIELD}. Note, * this does not mean that all {@code XProperty}s in this map are fields. Due to JPA access rules single properties * can have different access type than the overall class access type. */
private final TreeMap<String, XProperty> fieldAccessMap;
Constains the properties which must be returned in case the class is accessed via AccessType.Property. Note, this does not mean that all XPropertys in this map are properties/methods. Due to JPA access rules single properties can have different access type than the overall class access type.
/** * Constains the properties which must be returned in case the class is accessed via {@code AccessType.Property}. Note, * this does not mean that all {@code XProperty}s in this map are properties/methods. Due to JPA access rules single properties * can have different access type than the overall class access type. */
private final TreeMap<String, XProperty> propertyAccessMap;
The class for which this container is created.
/** * The class for which this container is created. */
private final XClass xClass; private final XClass entityAtStake; PropertyContainer(XClass clazz, XClass entityAtStake) { this.xClass = clazz; this.entityAtStake = entityAtStake; explicitClassDefinedAccessType = determineClassDefinedAccessStrategy(); // first add all properties to field and property map 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; } // see "2.3.2 Explicit Access Type" of JPA 2 spec // the access type for this property is explicitly set to AccessType.FIELD, hence we have to // use field access for this property even if the default access type for the class is AccessType.PROPERTY 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() ); // see "2.3.2 Explicit Access Type" of JPA 2 spec // the access type for this property is explicitly set to AccessType.PROPERTY, hence we have to // return use method access even if the default class access type is AccessType.FIELD if (accessType == AccessType.PROPERTY) { fieldAccessMap.put(property.getName(), property); } else { LOG.debug( "Placing @Access(AccessType.PROPERTY) on a field does not have any effect." ); } } }
Retrieves all properties from the xClass with the specified access type. This method does not take any jpa access rules/annotations into account yet.
Params:
  • access – The access type - AccessType.FIELD or AccessType.Property
Returns:A maps of the properties with the given access type keyed against their property name
/** * Retrieves all properties from the {@code xClass} with the specified access type. This method does not take * any jpa access rules/annotations into account yet. * * @param access The access type - {@code AccessType.FIELD} or {@code AccessType.Property} * * @return A maps of the properties with the given access type keyed against their property name */
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" ); } //order so that property are used in the same order when binding native query 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) { //TODO make those hardcoded tests more portable (through the bytecode provider?) 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() ); } }