/* * Copyright (c) 2009, Red Hat Middleware LLC 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 Middleware LLC. * * 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.ejb.metamodel; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.persistence.metamodel.Attribute; import javax.persistence.metamodel.IdentifiableType; import javax.persistence.metamodel.SingularAttribute; import org.hibernate.annotations.common.AssertionFailure; import org.hibernate.ejb.internal.EntityManagerMessageLogger; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.mapping.Component; import org.hibernate.mapping.KeyValue; import org.hibernate.mapping.MappedSuperclass; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.jboss.logging.Logger; /** * Defines a context for storing information during the building of the {@link MetamodelImpl}. * <p/> * This contextual information includes data needing to be processed in a second pass as well as * cross-references into the built metamodel classes. * <p/> * At the end of the day, clients are interested in the {@link #getEntityTypeMap} and {@link #getEmbeddableTypeMap} * results, which represent all the registered {@linkplain #registerEntityType entities} and * {@linkplain #registerEmbeddedableType embeddables} respectively. * * @author Steve Ebersole * @author Emmanuel Bernard */ class MetadataContext { private static final EntityManagerMessageLogger LOG = Logger.getMessageLogger(EntityManagerMessageLogger.class, MetadataContext.class.getName()); private final SessionFactoryImplementor sessionFactory; private final AttributeFactory attributeFactory = new AttributeFactory( this ); private Map<Class<?>,EntityTypeImpl<?>> entityTypes = new HashMap<Class<?>, EntityTypeImpl<?>>(); private Map<String,EntityTypeImpl<?>> entityTypesByEntityName = new HashMap<String, EntityTypeImpl<?>>(); private Map<PersistentClass,EntityTypeImpl<?>> entityTypesByPersistentClass = new HashMap<PersistentClass,EntityTypeImpl<?>>(); private Map<Class<?>, EmbeddableTypeImpl<?>> embeddables = new HashMap<Class<?>, EmbeddableTypeImpl<?>>(); private Map<MappedSuperclass, MappedSuperclassTypeImpl<?>> mappedSuperclassByMappedSuperclassMapping = new HashMap<MappedSuperclass,MappedSuperclassTypeImpl<?>>(); //this list contains MappedSuperclass and EntityTypes ordered by superclass first private List<Object> orderedMappings = new ArrayList<Object>(); /** * Stack of PersistentClass being process. Last in the list is the highest in the stack. * */ private List<PersistentClass> stackOfPersistentClassesBeingProcessed = new ArrayList<PersistentClass>(); private Map<MappedSuperclassTypeImpl<?>, PersistentClass> mappedSuperClassTypeToPersistentClass = new HashMap<MappedSuperclassTypeImpl<?>, PersistentClass>(); public MetadataContext(SessionFactoryImplementor sessionFactory) { this.sessionFactory = sessionFactory; } /*package*/ SessionFactoryImplementor getSessionFactory() { return sessionFactory; } /** * Retrieves the {@linkplain Class java type} to {@link EntityTypeImpl} map. * * @return The {@linkplain Class java type} to {@link EntityTypeImpl} map. */ public Map<Class<?>, EntityTypeImpl<?>> getEntityTypeMap() { return Collections.unmodifiableMap( entityTypes ); } public Map<Class<?>, EmbeddableTypeImpl<?>> getEmbeddableTypeMap() { return Collections.unmodifiableMap( embeddables ); } /*package*/ void registerEntityType(PersistentClass persistentClass, EntityTypeImpl<?> entityType) { entityTypes.put( entityType.getBindableJavaType(), entityType ); entityTypesByEntityName.put( persistentClass.getEntityName(), entityType ); entityTypesByPersistentClass.put( persistentClass, entityType ); orderedMappings.add( persistentClass ); } /*package*/ void registerEmbeddedableType(EmbeddableTypeImpl<?> embeddableType) { embeddables.put( embeddableType.getJavaType(), embeddableType ); } /*package*/ void registerMappedSuperclassType(MappedSuperclass mappedSuperclass, MappedSuperclassTypeImpl<?> mappedSuperclassType) { mappedSuperclassByMappedSuperclassMapping.put( mappedSuperclass, mappedSuperclassType ); orderedMappings.add( mappedSuperclass ); mappedSuperClassTypeToPersistentClass.put( mappedSuperclassType, getEntityWorkedOn() ); } /** * Given a Hibernate {@link PersistentClass}, locate the corresponding JPA {@link org.hibernate.type.EntityType} * implementation. May retur null if the given {@link PersistentClass} has not yet been processed. * * @param persistentClass The Hibernate (config time) metamodel instance representing an entity. * @return Tne corresponding JPA {@link org.hibernate.type.EntityType}, or null if not yet processed. */ public EntityTypeImpl<?> locateEntityType(PersistentClass persistentClass) { return entityTypesByPersistentClass.get( persistentClass ); } /** * Given a Java {@link Class}, locate the corresponding JPA {@link org.hibernate.type.EntityType}. May * return null which could means that no such mapping exists at least at this time. * * @param javaType The java class. * @return The corresponding JPA {@link org.hibernate.type.EntityType}, or null. */ public EntityTypeImpl<?> locateEntityType(Class<?> javaType) { return entityTypes.get( javaType ); } /** * Given an entity-name, locate the corresponding JPA {@link org.hibernate.type.EntityType}. May * return null which could means that no such mapping exists at least at this time. * * @param entityName The entity-name. * @return The corresponding JPA {@link org.hibernate.type.EntityType}, or null. */ public EntityTypeImpl<?> locateEntityType(String entityName) { return entityTypesByEntityName.get( entityName ); } @SuppressWarnings({ "unchecked" }) public void wrapUp() { LOG.trace("Wrapping up metadata context..."); //we need to process types from superclasses to subclasses for (Object mapping : orderedMappings) { if ( PersistentClass.class.isAssignableFrom( mapping.getClass() ) ) { @SuppressWarnings( "unchecked" ) final PersistentClass safeMapping = (PersistentClass) mapping; LOG.trace("Starting entity [" + safeMapping.getEntityName() + "]"); try { final EntityTypeImpl<?> jpa2Mapping = entityTypesByPersistentClass.get( safeMapping ); applyIdMetadata( safeMapping, jpa2Mapping ); applyVersionAttribute( safeMapping, jpa2Mapping ); Iterator<Property> properties = safeMapping.getDeclaredPropertyIterator(); while ( properties.hasNext() ) { final Property property = properties.next(); if ( property.getValue() == safeMapping.getIdentifierMapper() ) { // property represents special handling for id-class mappings but we have already // accounted for the embedded property mappings in #applyIdMetadata && // #buildIdClassAttributes continue; } if ( safeMapping.isVersioned() && property == safeMapping.getVersion() ) { // skip the version property, it was already handled previously. continue; } final Attribute attribute = attributeFactory.buildAttribute( jpa2Mapping, property ); if ( attribute != null ) { jpa2Mapping.getBuilder().addAttribute( attribute ); } } jpa2Mapping.lock(); populateStaticMetamodel( jpa2Mapping ); } finally { LOG.trace("Completed entity [" + safeMapping.getEntityName() + "]"); } } else if ( MappedSuperclass.class.isAssignableFrom( mapping.getClass() ) ) { @SuppressWarnings( "unchecked" ) final MappedSuperclass safeMapping = (MappedSuperclass) mapping; LOG.trace("Starting mapped superclass [" + safeMapping.getMappedClass().getName() + "]"); try { final MappedSuperclassTypeImpl<?> jpa2Mapping = mappedSuperclassByMappedSuperclassMapping.get( safeMapping ); applyIdMetadata( safeMapping, jpa2Mapping ); applyVersionAttribute( safeMapping, jpa2Mapping ); Iterator<Property> properties = safeMapping.getDeclaredPropertyIterator(); while ( properties.hasNext() ) { final Property property = properties.next(); if ( safeMapping.isVersioned() && property == safeMapping.getVersion() ) { // skip the version property, it was already handled previously. continue; } final Attribute attribute = attributeFactory.buildAttribute( jpa2Mapping, property ); if ( attribute != null ) { jpa2Mapping.getBuilder().addAttribute( attribute ); } } jpa2Mapping.lock(); populateStaticMetamodel( jpa2Mapping ); } finally { LOG.trace("Completed mapped superclass [" + safeMapping.getMappedClass().getName() + "]"); } } else { throw new AssertionFailure( "Unexpected mapping type: " + mapping.getClass() ); } } for ( EmbeddableTypeImpl embeddable : embeddables.values() ) { populateStaticMetamodel( embeddable ); } } private <X> void applyIdMetadata(PersistentClass persistentClass, EntityTypeImpl<X> jpaEntityType) { if ( persistentClass.hasIdentifierProperty() ) { final Property declaredIdentifierProperty = persistentClass.getDeclaredIdentifierProperty(); if (declaredIdentifierProperty != null) { jpaEntityType.getBuilder().applyIdAttribute( attributeFactory.buildIdAttribute( jpaEntityType, declaredIdentifierProperty ) ); } } else if ( persistentClass.hasIdentifierMapper() ) { @SuppressWarnings( "unchecked") Iterator<Property> propertyIterator = persistentClass.getIdentifierMapper().getPropertyIterator(); Set<SingularAttribute<? super X, ?>> attributes = buildIdClassAttributes( jpaEntityType, propertyIterator ); jpaEntityType.getBuilder().applyIdClassAttributes( attributes ); } else { final KeyValue value = persistentClass.getIdentifier(); if (value instanceof Component ) { final Component component = ( Component ) value; if ( component.getPropertySpan() > 1 ) { //FIXME we are an Hibernate embedded id (ie not type) } else { //FIXME take care of declared vs non declared property jpaEntityType.getBuilder().applyIdAttribute( attributeFactory.buildIdAttribute( jpaEntityType, (Property) component.getPropertyIterator().next() ) ); } } } } private <X> void applyIdMetadata(MappedSuperclass mappingType, MappedSuperclassTypeImpl<X> jpaMappingType) { if ( mappingType.hasIdentifierProperty() ) { final Property declaredIdentifierProperty = mappingType.getDeclaredIdentifierProperty(); if (declaredIdentifierProperty != null) { jpaMappingType.getBuilder().applyIdAttribute( attributeFactory.buildIdAttribute( jpaMappingType, declaredIdentifierProperty ) ); } } //an MappedSuperclass can have no identifier if the id is set below in the hierarchy else if ( mappingType.getIdentifierMapper() != null ){ @SuppressWarnings( "unchecked") Iterator<Property> propertyIterator = mappingType.getIdentifierMapper().getPropertyIterator(); Set<SingularAttribute<? super X, ?>> attributes = buildIdClassAttributes( jpaMappingType, propertyIterator ); jpaMappingType.getBuilder().applyIdClassAttributes( attributes ); } } private <X> void applyVersionAttribute(PersistentClass persistentClass, EntityTypeImpl<X> jpaEntityType) { final Property declaredVersion = persistentClass.getDeclaredVersion(); if (declaredVersion != null) { jpaEntityType.getBuilder().applyVersionAttribute( attributeFactory.buildVersionAttribute( jpaEntityType, declaredVersion ) ); } } private <X> void applyVersionAttribute(MappedSuperclass mappingType, MappedSuperclassTypeImpl<X> jpaMappingType) { final Property declaredVersion = mappingType.getDeclaredVersion(); if ( declaredVersion != null ) { jpaMappingType.getBuilder().applyVersionAttribute( attributeFactory.buildVersionAttribute( jpaMappingType, declaredVersion ) ); } } private <X> Set<SingularAttribute<? super X, ?>> buildIdClassAttributes( AbstractIdentifiableType<X> ownerType, Iterator<Property> propertyIterator) { LOG.trace("Building old-school composite identifier [" + ownerType.getJavaType().getName() + "]"); Set<SingularAttribute<? super X, ?>> attributes = new HashSet<SingularAttribute<? super X, ?>>(); while ( propertyIterator.hasNext() ) { attributes.add( attributeFactory.buildIdAttribute( ownerType, propertyIterator.next() ) ); } return attributes; } private <X> void populateStaticMetamodel(AbstractManagedType<X> managedType) { final Class<X> managedTypeClass = managedType.getJavaType(); final String metamodelClassName = managedTypeClass.getName() + "_"; try { final Class metamodelClass = Class.forName( metamodelClassName, true, managedTypeClass.getClassLoader() ); // we found the class; so populate it... registerAttributes( metamodelClass, managedType ); } catch ( ClassNotFoundException ignore ) { // nothing to do... } // todo : this does not account for @MappeSuperclass, mainly because this is not being tracked in our // internal metamodel as populated from the annotatios properly AbstractManagedType<? super X> superType = managedType.getSupertype(); if ( superType != null ) { populateStaticMetamodel( superType ); } } private final Set<Class> processedMetamodelClasses = new HashSet<Class>(); private <X> void registerAttributes(Class metamodelClass, AbstractManagedType<X> managedType) { if ( ! processedMetamodelClasses.add( metamodelClass ) ) { return; } // push the attributes on to the metamodel class... for ( Attribute<X, ?> attribute : managedType.getDeclaredAttributes() ) { registerAttribute( metamodelClass, attribute ); } if ( IdentifiableType.class.isInstance( managedType ) ) { final AbstractIdentifiableType<X> entityType = ( AbstractIdentifiableType<X> ) managedType; // handle version if ( entityType.hasDeclaredVersionAttribute() ) { registerAttribute( metamodelClass, entityType.getDeclaredVersion() ); } // handle id-class mappings specially if ( ! entityType.hasSingleIdAttribute() ) { final Set<SingularAttribute<? super X, ?>> attributes = entityType.getIdClassAttributes(); if ( attributes != null ) { for ( SingularAttribute<? super X, ?> attribute : attributes ) { registerAttribute( metamodelClass, attribute ); } } } } } private <X> void registerAttribute(Class metamodelClass, Attribute<X, ?> attribute) { final String name = attribute.getName(); try { Field field = metamodelClass.getDeclaredField( name ); try { if ( ! field.isAccessible() ) { // should be public anyway, but to be sure... field.setAccessible( true ); } field.set( null, attribute ); } catch ( IllegalAccessException e ) { // todo : exception type? throw new AssertionFailure( "Unable to inject static metamodel attribute : " + metamodelClass.getName() + '#' + name, e ); } catch ( IllegalArgumentException e ) { // most likely a mismatch in the type we are injecting and the defined field; this represents a // mismatch in how the annotation processor interpretted the attribute and how our metamodel // and/or annotation binder did. // This is particularly the case as arrays are nto handled propery by the StaticMetamodel generator // throw new AssertionFailure( // "Illegal argument on static metamodel field injection : " + metamodelClass.getName() + '#' + name // + "; expected type : " + attribute.getClass().getName() // + "; encountered type : " + field.getType().getName() // ); LOG.illegalArgumentOnStaticMetamodelFieldInjection(metamodelClass.getName(), name, attribute.getClass().getName(), field.getType().getName()); } } catch ( NoSuchFieldException e ) { LOG.unableToLocateStaticMetamodelField(metamodelClass.getName(), name); // throw new AssertionFailure( // "Unable to locate static metamodel field : " + metamodelClass.getName() + '#' + name // ); } } public MappedSuperclassTypeImpl<?> locateMappedSuperclassType(MappedSuperclass mappedSuperclass) { return mappedSuperclassByMappedSuperclassMapping.get(mappedSuperclass); } public void pushEntityWorkedOn(PersistentClass persistentClass) { stackOfPersistentClassesBeingProcessed.add(persistentClass); } public void popEntityWorkedOn(PersistentClass persistentClass) { final PersistentClass stackTop = stackOfPersistentClassesBeingProcessed.remove( stackOfPersistentClassesBeingProcessed.size() - 1 ); if (stackTop != persistentClass) { throw new AssertionFailure( "Inconsistent popping: " + persistentClass.getEntityName() + " instead of " + stackTop.getEntityName() ); } } private PersistentClass getEntityWorkedOn() { return stackOfPersistentClassesBeingProcessed.get( stackOfPersistentClassesBeingProcessed.size() - 1 ); } public PersistentClass getPersistentClassHostingProperties(MappedSuperclassTypeImpl<?> mappedSuperclassType) { final PersistentClass persistentClass = mappedSuperClassTypeToPersistentClass.get( mappedSuperclassType ); if (persistentClass == null) { throw new AssertionFailure( "Could not find PersistentClass for MappedSuperclassType: " + mappedSuperclassType.getJavaType() ); } return persistentClass; } }