Java tutorial
/* * Hibernate, Relational Persistence for Idiomatic Java * * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. */ package org.hibernate.tuple.entity; import java.io.Serializable; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.hibernate.EntityMode; import org.hibernate.EntityNameResolver; import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.id.Assigned; import org.hibernate.loader.PropertyPath; import org.hibernate.mapping.Component; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.metamodel.spi.MetamodelImplementor; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.property.access.spi.Getter; import org.hibernate.property.access.spi.Setter; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.ProxyFactory; import org.hibernate.tuple.IdentifierProperty; import org.hibernate.tuple.Instantiator; import org.hibernate.type.AssociationType; import org.hibernate.type.ComponentType; import org.hibernate.type.CompositeType; import org.hibernate.type.EntityType; import org.hibernate.type.Type; /** * Support for tuplizers relating to entities. * * @author Steve Ebersole * @author Gavin King */ public abstract class AbstractEntityTuplizer implements EntityTuplizer { //TODO: currently keeps Getters and Setters (instead of PropertyAccessors) because of the way getGetter() and getSetter() are implemented currently; yuck! private final EntityMetamodel entityMetamodel; private final Getter idGetter; private final Setter idSetter; protected final Getter[] getters; protected final Setter[] setters; protected final int propertySpan; protected final boolean hasCustomAccessors; private final Instantiator instantiator; private final ProxyFactory proxyFactory; private final CompositeType identifierMapperType; public Type getIdentifierMapperType() { return identifierMapperType; } /** * Build an appropriate Getter for the given property. * * @param mappedProperty The property to be accessed via the built Getter. * @param mappedEntity The entity information regarding the mapped entity owning this property. * * @return An appropriate Getter instance. */ protected abstract Getter buildPropertyGetter(Property mappedProperty, PersistentClass mappedEntity); /** * Build an appropriate Setter for the given property. * * @param mappedProperty The property to be accessed via the built Setter. * @param mappedEntity The entity information regarding the mapped entity owning this property. * * @return An appropriate Setter instance. */ protected abstract Setter buildPropertySetter(Property mappedProperty, PersistentClass mappedEntity); /** * Build an appropriate Instantiator for the given mapped entity. * * @param mappingInfo The mapping information regarding the mapped entity. * * @return An appropriate Instantiator instance. */ protected abstract Instantiator buildInstantiator(EntityMetamodel entityMetamodel, PersistentClass mappingInfo); /** * Build an appropriate ProxyFactory for the given mapped entity. * * @param mappingInfo The mapping information regarding the mapped entity. * @param idGetter The constructed Getter relating to the entity's id property. * @param idSetter The constructed Setter relating to the entity's id property. * * @return An appropriate ProxyFactory instance. */ protected abstract ProxyFactory buildProxyFactory(PersistentClass mappingInfo, Getter idGetter, Setter idSetter); /** * Constructs a new AbstractEntityTuplizer instance. * * @param entityMetamodel The "interpreted" information relating to the mapped entity. * @param mappingInfo The parsed "raw" mapping data relating to the given entity. */ public AbstractEntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappingInfo) { this.entityMetamodel = entityMetamodel; if (!entityMetamodel.getIdentifierProperty().isVirtual()) { idGetter = buildPropertyGetter(mappingInfo.getIdentifierProperty(), mappingInfo); idSetter = buildPropertySetter(mappingInfo.getIdentifierProperty(), mappingInfo); } else { idGetter = null; idSetter = null; } propertySpan = entityMetamodel.getPropertySpan(); getters = new Getter[propertySpan]; setters = new Setter[propertySpan]; Iterator itr = mappingInfo.getPropertyClosureIterator(); boolean foundCustomAccessor = false; int i = 0; while (itr.hasNext()) { //TODO: redesign how PropertyAccessors are acquired... Property property = (Property) itr.next(); getters[i] = buildPropertyGetter(property, mappingInfo); setters[i] = buildPropertySetter(property, mappingInfo); if (!property.isBasicPropertyAccessor()) { foundCustomAccessor = true; } i++; } hasCustomAccessors = foundCustomAccessor; instantiator = buildInstantiator(entityMetamodel, mappingInfo); // if ( entityMetamodel.isLazy() && !entityMetamodel.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { if (entityMetamodel.isLazy()) { proxyFactory = buildProxyFactory(mappingInfo, idGetter, idSetter); if (proxyFactory == null) { entityMetamodel.setLazy(false); } } else { proxyFactory = null; } Component mapper = mappingInfo.getIdentifierMapper(); if (mapper == null) { identifierMapperType = null; mappedIdentifierValueMarshaller = null; } else { identifierMapperType = (CompositeType) mapper.getType(); mappedIdentifierValueMarshaller = buildMappedIdentifierValueMarshaller(getEntityName(), getFactory(), (ComponentType) entityMetamodel.getIdentifierProperty().getType(), (ComponentType) identifierMapperType); } } /** * Retrieves the defined entity-name for the tuplized entity. * * @return The entity-name. */ protected String getEntityName() { return entityMetamodel.getName(); } /** * Retrieves the defined entity-names for any subclasses defined for this * entity. * * @return Any subclass entity-names. */ protected Set getSubclassEntityNames() { return entityMetamodel.getSubclassEntityNames(); } @Override public Serializable getIdentifier(Object entity) throws HibernateException { return getIdentifier(entity, null); } @Override public Serializable getIdentifier(Object entity, SharedSessionContractImplementor session) { final Object id; if (entityMetamodel.getIdentifierProperty().isEmbedded()) { id = entity; } else if (HibernateProxy.class.isInstance(entity)) { id = ((HibernateProxy) entity).getHibernateLazyInitializer().getIdentifier(); } else { if (idGetter == null) { if (identifierMapperType == null) { throw new HibernateException("The class has no identifier property: " + getEntityName()); } else { id = mappedIdentifierValueMarshaller.getIdentifier(entity, getEntityMode(), session); } } else { id = idGetter.get(entity); } } try { return (Serializable) id; } catch (ClassCastException cce) { StringBuilder msg = new StringBuilder("Identifier classes must be serializable. "); if (id != null) { msg.append(id.getClass().getName()).append(" is not serializable. "); } if (cce.getMessage() != null) { msg.append(cce.getMessage()); } throw new ClassCastException(msg.toString()); } } @Override public void setIdentifier(Object entity, Serializable id) throws HibernateException { // 99% of the time the session is not needed. Its only needed for certain brain-dead // interpretations of JPA 2 "derived identity" support setIdentifier(entity, id, null); } @Override public void setIdentifier(Object entity, Serializable id, SharedSessionContractImplementor session) { if (entityMetamodel.getIdentifierProperty().isEmbedded()) { if (entity != id) { CompositeType copier = (CompositeType) entityMetamodel.getIdentifierProperty().getType(); copier.setPropertyValues(entity, copier.getPropertyValues(id, getEntityMode()), getEntityMode()); } } else if (idSetter != null) { idSetter.set(entity, id, getFactory()); } else if (identifierMapperType != null) { mappedIdentifierValueMarshaller.setIdentifier(entity, id, getEntityMode(), session); } } private static interface MappedIdentifierValueMarshaller { public Object getIdentifier(Object entity, EntityMode entityMode, SharedSessionContractImplementor session); public void setIdentifier(Object entity, Serializable id, EntityMode entityMode, SharedSessionContractImplementor session); } private final MappedIdentifierValueMarshaller mappedIdentifierValueMarshaller; private static MappedIdentifierValueMarshaller buildMappedIdentifierValueMarshaller(String entityName, SessionFactoryImplementor sessionFactory, ComponentType mappedIdClassComponentType, ComponentType virtualIdComponent) { // so basically at this point we know we have a "mapped" composite identifier // which is an awful way to say that the identifier is represented differently // in the entity and in the identifier value. The incoming value should // be an instance of the mapped identifier class (@IdClass) while the incoming entity // should be an instance of the entity class as defined by metamodel. // // However, even within that we have 2 potential scenarios: // 1) @IdClass types and entity @Id property types match // - return a NormalMappedIdentifierValueMarshaller // 2) They do not match // - return a IncrediblySillyJpaMapsIdMappedIdentifierValueMarshaller boolean wereAllEquivalent = true; // the sizes being off is a much bigger problem that should have been caught already... for (int i = 0; i < virtualIdComponent.getSubtypes().length; i++) { if (virtualIdComponent.getSubtypes()[i].isEntityType() && !mappedIdClassComponentType.getSubtypes()[i].isEntityType()) { wereAllEquivalent = false; break; } } return wereAllEquivalent ? new NormalMappedIdentifierValueMarshaller(virtualIdComponent, mappedIdClassComponentType) : new IncrediblySillyJpaMapsIdMappedIdentifierValueMarshaller(entityName, sessionFactory, virtualIdComponent, mappedIdClassComponentType); } private static class NormalMappedIdentifierValueMarshaller implements MappedIdentifierValueMarshaller { private final ComponentType virtualIdComponent; private final ComponentType mappedIdentifierType; private NormalMappedIdentifierValueMarshaller(ComponentType virtualIdComponent, ComponentType mappedIdentifierType) { this.virtualIdComponent = virtualIdComponent; this.mappedIdentifierType = mappedIdentifierType; } @Override public Object getIdentifier(Object entity, EntityMode entityMode, SharedSessionContractImplementor session) { Object id = mappedIdentifierType.instantiate(entityMode); final Object[] propertyValues = virtualIdComponent.getPropertyValues(entity, entityMode); mappedIdentifierType.setPropertyValues(id, propertyValues, entityMode); return id; } @Override public void setIdentifier(Object entity, Serializable id, EntityMode entityMode, SharedSessionContractImplementor session) { virtualIdComponent.setPropertyValues(entity, mappedIdentifierType.getPropertyValues(id, session), entityMode); } } private static class IncrediblySillyJpaMapsIdMappedIdentifierValueMarshaller implements MappedIdentifierValueMarshaller { private final String entityName; private final SessionFactoryImplementor sessionFactory; private final ComponentType virtualIdComponent; private final ComponentType mappedIdentifierType; private IncrediblySillyJpaMapsIdMappedIdentifierValueMarshaller(String entityName, SessionFactoryImplementor sessionFactory, ComponentType virtualIdComponent, ComponentType mappedIdentifierType) { this.sessionFactory = sessionFactory; this.entityName = entityName; this.virtualIdComponent = virtualIdComponent; this.mappedIdentifierType = mappedIdentifierType; } @Override public Object getIdentifier(Object entity, EntityMode entityMode, SharedSessionContractImplementor session) { final Object id = mappedIdentifierType.instantiate(entityMode); final Object[] propertyValues = virtualIdComponent.getPropertyValues(entity, entityMode); final Type[] subTypes = virtualIdComponent.getSubtypes(); final Type[] copierSubTypes = mappedIdentifierType.getSubtypes(); final int length = subTypes.length; for (int i = 0; i < length; i++) { if (propertyValues[i] == null) { throw new HibernateException("No part of a composite identifier may be null"); } //JPA 2 @MapsId + @IdClass points to the pk of the entity if (subTypes[i].isAssociationType() && !copierSubTypes[i].isAssociationType()) { propertyValues[i] = determineEntityId(propertyValues[i], (AssociationType) subTypes[i], session, sessionFactory); } } mappedIdentifierType.setPropertyValues(id, propertyValues, entityMode); return id; } @Override public void setIdentifier(Object entity, Serializable id, EntityMode entityMode, SharedSessionContractImplementor session) { final Object[] extractedValues = mappedIdentifierType.getPropertyValues(id, entityMode); final Object[] injectionValues = new Object[extractedValues.length]; final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); final MetamodelImplementor metamodel = sessionFactory.getMetamodel(); for (int i = 0; i < virtualIdComponent.getSubtypes().length; i++) { final Type virtualPropertyType = virtualIdComponent.getSubtypes()[i]; final Type idClassPropertyType = mappedIdentifierType.getSubtypes()[i]; if (virtualPropertyType.isEntityType() && !idClassPropertyType.isEntityType()) { if (session == null) { throw new AssertionError( "Deprecated version of getIdentifier (no session) was used but session was required"); } final String associatedEntityName = ((EntityType) virtualPropertyType) .getAssociatedEntityName(); final EntityKey entityKey = session.generateEntityKey((Serializable) extractedValues[i], metamodel.entityPersister(associatedEntityName)); // it is conceivable there is a proxy, so check that first Object association = persistenceContext.getProxy(entityKey); if (association == null) { // otherwise look for an initialized version association = persistenceContext.getEntity(entityKey); if (association == null) { // get the association out of the entity itself association = metamodel.entityPersister(entityName).getPropertyValue(entity, virtualIdComponent.getPropertyNames()[i]); } } injectionValues[i] = association; } else { injectionValues[i] = extractedValues[i]; } } virtualIdComponent.setPropertyValues(entity, injectionValues, entityMode); } } private static Serializable determineEntityId(Object entity, AssociationType associationType, SharedSessionContractImplementor session, SessionFactoryImplementor sessionFactory) { if (entity == null) { return null; } if (HibernateProxy.class.isInstance(entity)) { // entity is a proxy, so we know it is not transient; just return ID from proxy return ((HibernateProxy) entity).getHibernateLazyInitializer().getIdentifier(); } if (session != null) { final EntityEntry pcEntry = session.getPersistenceContextInternal().getEntry(entity); if (pcEntry != null) { // entity managed; return ID. return pcEntry.getId(); } } final EntityPersister persister = resolveEntityPersister(entity, associationType, session, sessionFactory); return persister.getIdentifier(entity, session); } private static EntityPersister resolveEntityPersister(Object entity, AssociationType associationType, SharedSessionContractImplementor session, SessionFactoryImplementor sessionFactory) { assert sessionFactory != null; if (session != null) { return session.getEntityPersister(associationType.getAssociatedEntityName(sessionFactory), entity); } String entityName = null; final MetamodelImplementor metamodel = sessionFactory.getMetamodel(); for (EntityNameResolver entityNameResolver : metamodel.getEntityNameResolvers()) { entityName = entityNameResolver.resolveEntityName(entity); if (entityName != null) { break; } } if (entityName == null) { // old fall-back entityName = entity.getClass().getName(); } return metamodel.entityPersister(entityName); } @Override public void resetIdentifier(Object entity, Serializable currentId, Object currentVersion) { // 99% of the time the session is not needed. Its only needed for certain brain-dead // interpretations of JPA 2 "derived identity" support resetIdentifier(entity, currentId, currentVersion, null); } @Override public void resetIdentifier(Object entity, Serializable currentId, Object currentVersion, SharedSessionContractImplementor session) { //noinspection StatementWithEmptyBody final IdentifierProperty identifierProperty = entityMetamodel.getIdentifierProperty(); if (identifierProperty.getIdentifierGenerator() instanceof Assigned) { } else { //reset the id Serializable result = identifierProperty.getUnsavedValue().getDefaultValue(currentId); setIdentifier(entity, result, session); //reset the version VersionProperty versionProperty = entityMetamodel.getVersionProperty(); if (entityMetamodel.isVersioned()) { setPropertyValue(entity, entityMetamodel.getVersionPropertyIndex(), versionProperty.getUnsavedValue().getDefaultValue(currentVersion)); } } } @Override public Object getVersion(Object entity) throws HibernateException { if (!entityMetamodel.isVersioned()) { return null; } return getters[entityMetamodel.getVersionPropertyIndex()].get(entity); } protected boolean shouldGetAllProperties(Object entity) { final BytecodeEnhancementMetadata bytecodeEnhancementMetadata = getEntityMetamodel() .getBytecodeEnhancementMetadata(); if (!bytecodeEnhancementMetadata.isEnhancedForLazyLoading()) { return true; } return !bytecodeEnhancementMetadata.hasUnFetchedAttributes(entity); } @Override public Object[] getPropertyValues(Object entity) { final BytecodeEnhancementMetadata enhancementMetadata = entityMetamodel.getBytecodeEnhancementMetadata(); final LazyAttributesMetadata lazyAttributesMetadata = enhancementMetadata.getLazyAttributesMetadata(); final int span = entityMetamodel.getPropertySpan(); final String[] propertyNames = entityMetamodel.getPropertyNames(); final Object[] result = new Object[span]; for (int j = 0; j < span; j++) { final String propertyName = propertyNames[j]; // if the attribute is not lazy (bytecode sense), we can just use the value from the instance // if the attribute is lazy but has been initialized we can just use the value from the instance // todo : there should be a third case here when we merge transient instances if (!lazyAttributesMetadata.isLazyAttribute(propertyName) || enhancementMetadata.isAttributeLoaded(entity, propertyName)) { result[j] = getters[j].get(entity); } else { result[j] = LazyPropertyInitializer.UNFETCHED_PROPERTY; } } return result; } @Override public Object[] getPropertyValuesToInsert(Object entity, Map mergeMap, SharedSessionContractImplementor session) { final int span = entityMetamodel.getPropertySpan(); final Object[] result = new Object[span]; for (int j = 0; j < span; j++) { result[j] = getters[j].getForInsert(entity, mergeMap, session); } return result; } @Override public Object getPropertyValue(Object entity, int i) throws HibernateException { return getters[i].get(entity); } @Override public Object getPropertyValue(Object entity, String propertyPath) throws HibernateException { int loc = propertyPath.indexOf('.'); String basePropertyName = loc > 0 ? propertyPath.substring(0, loc) : propertyPath; //final int index = entityMetamodel.getPropertyIndexOrNull( basePropertyName ); Integer index = entityMetamodel.getPropertyIndexOrNull(basePropertyName); if (index == null) { propertyPath = PropertyPath.IDENTIFIER_MAPPER_PROPERTY + "." + propertyPath; loc = propertyPath.indexOf('.'); basePropertyName = loc > 0 ? propertyPath.substring(0, loc) : propertyPath; } index = entityMetamodel.getPropertyIndexOrNull(basePropertyName); final Object baseValue = getPropertyValue(entity, index); if (loc > 0) { if (baseValue == null) { return null; } return getComponentValue((ComponentType) entityMetamodel.getPropertyTypes()[index], baseValue, propertyPath.substring(loc + 1)); } else { return baseValue; } } /** * Extract a component property value. * * @param type The component property types. * @param component The component instance itself. * @param propertyPath The property path for the property to be extracted. * * @return The property value extracted. */ protected Object getComponentValue(ComponentType type, Object component, String propertyPath) { final int loc = propertyPath.indexOf('.'); final String basePropertyName = loc > 0 ? propertyPath.substring(0, loc) : propertyPath; final int index = findSubPropertyIndex(type, basePropertyName); final Object baseValue = type.getPropertyValue(component, index); if (loc > 0) { if (baseValue == null) { return null; } return getComponentValue((ComponentType) type.getSubtypes()[index], baseValue, propertyPath.substring(loc + 1)); } else { return baseValue; } } private int findSubPropertyIndex(ComponentType type, String subPropertyName) { final String[] propertyNames = type.getPropertyNames(); for (int index = 0; index < propertyNames.length; index++) { if (subPropertyName.equals(propertyNames[index])) { return index; } } throw new MappingException("component property not found: " + subPropertyName); } @Override public void setPropertyValues(Object entity, Object[] values) throws HibernateException { boolean setAll = !entityMetamodel.hasLazyProperties(); final SessionFactoryImplementor factory = getFactory(); for (int j = 0; j < entityMetamodel.getPropertySpan(); j++) { if (setAll || values[j] != LazyPropertyInitializer.UNFETCHED_PROPERTY) { setters[j].set(entity, values[j], factory); } } } @Override public void setPropertyValue(Object entity, int i, Object value) throws HibernateException { setters[i].set(entity, value, getFactory()); } @Override public void setPropertyValue(Object entity, String propertyName, Object value) throws HibernateException { setters[entityMetamodel.getPropertyIndex(propertyName)].set(entity, value, getFactory()); } @Override public final Object instantiate(Serializable id) throws HibernateException { // 99% of the time the session is not needed. Its only needed for certain brain-dead // interpretations of JPA 2 "derived identity" support return instantiate(id, null); } @Override public final Object instantiate(Serializable id, SharedSessionContractImplementor session) { Object result = getInstantiator().instantiate(id); if (id != null) { setIdentifier(result, id, session); } return result; } @Override public final Object instantiate() throws HibernateException { return instantiate(null, null); } @Override public void afterInitialize(Object entity, SharedSessionContractImplementor session) { } @Override public final boolean isInstance(Object object) { return getInstantiator().isInstance(object); } @Override public boolean hasProxy() { return entityMetamodel.isLazy() && !entityMetamodel.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); } @Override public final Object createProxy(Serializable id, SharedSessionContractImplementor session) { return getProxyFactory().getProxy(id, session); } @Override public boolean isLifecycleImplementor() { return false; } protected final EntityMetamodel getEntityMetamodel() { return entityMetamodel; } protected final SessionFactoryImplementor getFactory() { return entityMetamodel.getSessionFactory(); } protected final Instantiator getInstantiator() { return instantiator; } @Override public final ProxyFactory getProxyFactory() { return proxyFactory; } @Override public String toString() { return getClass().getName() + '(' + getEntityMetamodel().getName() + ')'; } @Override public Getter getIdentifierGetter() { return idGetter; } @Override public Getter getVersionGetter() { final EntityMetamodel entityMetamodel = getEntityMetamodel(); if (entityMetamodel.isVersioned()) { return getGetter(entityMetamodel.getVersionPropertyIndex()); } return null; } @Override public Getter getGetter(int i) { return getters[i]; } }