Package org.hibernate.loader.plan.exec.process.internal

Source Code of org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl

/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, 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.loader.plan.exec.process.internal;

import java.io.Serializable;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.jboss.logging.Logger;

import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.StaleObjectStateException;
import org.hibernate.WrongClassException;
import org.hibernate.engine.internal.TwoPhaseLoad;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.EntityUniqueKey;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.CoreLogging;
import org.hibernate.loader.EntityAliases;
import org.hibernate.loader.plan.exec.process.spi.EntityReferenceInitializer;
import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext;
import org.hibernate.loader.plan.exec.spi.EntityReferenceAliases;
import org.hibernate.loader.plan.spi.EntityFetch;
import org.hibernate.loader.plan.spi.EntityReference;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Loadable;
import org.hibernate.persister.entity.UniqueKeyLoadable;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;
import org.hibernate.type.VersionType;

import static org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext.EntityReferenceProcessingState;

/**
* @author Steve Ebersole
*/
public class EntityReferenceInitializerImpl implements EntityReferenceInitializer {
  private static final Logger log = CoreLogging.logger( EntityReferenceInitializerImpl.class );

  private final EntityReference entityReference;
  private final EntityReferenceAliases entityReferenceAliases;
  private final boolean isReturn;

  public EntityReferenceInitializerImpl(
      EntityReference entityReference,
      EntityReferenceAliases entityReferenceAliases) {
    this( entityReference, entityReferenceAliases, false );
  }

  public EntityReferenceInitializerImpl(
      EntityReference entityReference,
      EntityReferenceAliases entityReferenceAliases,
      boolean isRoot) {
    this.entityReference = entityReference;
    this.entityReferenceAliases = entityReferenceAliases;
    isReturn = isRoot;
  }

  @Override
  public EntityReference getEntityReference() {
    return entityReference;
  }

  @Override
  public void hydrateIdentifier(ResultSet resultSet, ResultSetProcessingContextImpl context) throws SQLException {

    final EntityReferenceProcessingState processingState = context.getProcessingState( entityReference );

    // get any previously registered identifier hydrated-state
    Object identifierHydratedForm = processingState.getIdentifierHydratedForm();
    if ( identifierHydratedForm == null ) {
      // if there is none, read it from the result set
      identifierHydratedForm = readIdentifierHydratedState( resultSet, context );

      // broadcast the fact that a hydrated identifier value just became associated with
      // this entity reference
      processingState.registerIdentifierHydratedForm( identifierHydratedForm );
    }
  }

  /**
   * Read the identifier state for the entity reference for the currently processing row in the ResultSet
   *
   * @param resultSet The ResultSet being processed
   * @param context The processing context
   *
   * @return The hydrated state
   *
   * @throws java.sql.SQLException Indicates a problem accessing the ResultSet
   */
  private Object readIdentifierHydratedState(ResultSet resultSet, ResultSetProcessingContext context)
      throws SQLException {
    try {
      return entityReference.getEntityPersister().getIdentifierType().hydrate(
          resultSet,
          entityReferenceAliases.getColumnAliases().getSuffixedKeyAliases(),
          context.getSession(),
          null
      );
    }
    catch (Exception e) {
      throw new HibernateException(
          "Encountered problem trying to hydrate identifier for entity ["
              + entityReference.getEntityPersister() + "]",
          e
      );
    }
  }

  @Override
  public void resolveEntityKey(ResultSet resultSet, ResultSetProcessingContextImpl context) {

    final EntityReferenceProcessingState processingState = context.getProcessingState( entityReference );

    // see if we already have an EntityKey associated with this EntityReference in the processing state.
    // if we do, this should have come from the optional entity identifier...
    final EntityKey entityKey = processingState.getEntityKey();
    if ( entityKey != null ) {
      log.debugf(
          "On call to EntityIdentifierReaderImpl#resolve [for %s], EntityKey was already known; " +
              "should only happen on root returns with an optional identifier specified"
      );
      return;
    }

    // Look for the hydrated form
    final Object identifierHydratedForm = processingState.getIdentifierHydratedForm();
    if ( identifierHydratedForm == null ) {
      // we need to register the missing identifier, but that happens later after all readers have had a chance
      // to resolve its EntityKey
      return;
    }

    final Type identifierType = entityReference.getEntityPersister().getIdentifierType();
    final Serializable resolvedId = (Serializable) identifierType.resolve(
        identifierHydratedForm,
        context.getSession(),
        null
    );
    if ( resolvedId != null ) {
      processingState.registerEntityKey(
          context.getSession().generateEntityKey( resolvedId, entityReference.getEntityPersister() )
      );
    }
  }

  @Override
  public void hydrateEntityState(ResultSet resultSet, ResultSetProcessingContextImpl context) {
    final EntityReferenceProcessingState processingState = context.getProcessingState( entityReference );

    // If there is no identifier for this entity reference for this row, nothing to do
    if ( processingState.isMissingIdentifier() ) {
      handleMissingIdentifier( context );
      return;
    }

    // make sure we have the EntityKey
    final EntityKey entityKey = processingState.getEntityKey();
    if ( entityKey == null ) {
      handleMissingIdentifier( context );
      return;
    }

    // Have we already hydrated this entity's state?
    if ( processingState.getEntityInstance() != null ) {
      return;
    }


    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // In getting here, we know that:
    //     1) We need to hydrate the entity state
    //    2) We have a valid EntityKey for the entity

    // see if we have an existing entry in the session for this EntityKey
    final Object existing = context.getSession().getEntityUsingInterceptor( entityKey );
    if ( existing != null ) {
      // It is previously associated with the Session, perform some checks
      if ( ! entityReference.getEntityPersister().isInstance( existing ) ) {
        throw new WrongClassException(
            "loaded object was of wrong class " + existing.getClass(),
            entityKey.getIdentifier(),
            entityReference.getEntityPersister().getEntityName()
        );
      }
      checkVersion( resultSet, context, entityKey, existing );

      // use the existing association as the hydrated state
      processingState.registerEntityInstance( existing );
      //context.registerHydratedEntity( entityReference, entityKey, existing );
      return;
    }

    // Otherwise, we need to load it from the ResultSet...

    // determine which entity instance to use.  Either the supplied one, or instantiate one
    Object optionalEntityInstance = null;
    if ( isReturn && context.shouldUseOptionalEntityInformation() ) {
      final EntityKey optionalEntityKey = ResultSetProcessorHelper.getOptionalObjectKey(
          context.getQueryParameters(),
          context.getSession()
      );
      if ( optionalEntityKey != null ) {
        if ( optionalEntityKey.equals( entityKey ) ) {
          optionalEntityInstance = context.getQueryParameters().getOptionalObject();
        }
      }
    }

    final String concreteEntityTypeName = getConcreteEntityTypeName( resultSet, context, entityKey );

    final Object entityInstance = optionalEntityInstance != null
        ? optionalEntityInstance
        : context.getSession().instantiate( concreteEntityTypeName, entityKey.getIdentifier() );

    processingState.registerEntityInstance( entityInstance );

    // need to hydrate it.
    // grab its state from the ResultSet and keep it in the Session
    // (but don't yet initialize the object itself)
    // note that we acquire LockMode.READ even if it was not requested
    log.trace( "hydrating entity state" );
    final LockMode requestedLockMode = context.resolveLockMode( entityReference );
    final LockMode lockModeToAcquire = requestedLockMode == LockMode.NONE
        ? LockMode.READ
        : requestedLockMode;

    loadFromResultSet(
        resultSet,
        context,
        entityInstance,
        concreteEntityTypeName,
        entityKey,
        lockModeToAcquire
    );
  }

  private void handleMissingIdentifier(ResultSetProcessingContext context) {
    if ( EntityFetch.class.isInstance( entityReference ) ) {
      final EntityFetch fetch = (EntityFetch) entityReference;
      final EntityType fetchedType = fetch.getFetchedType();
      if ( ! fetchedType.isOneToOne() ) {
        return;
      }

      final EntityReferenceProcessingState fetchOwnerState = context.getOwnerProcessingState( fetch );
      if ( fetchOwnerState == null ) {
        throw new IllegalStateException( "Could not locate fetch owner state" );
      }

      final EntityKey ownerEntityKey = fetchOwnerState.getEntityKey();
      if ( ownerEntityKey != null ) {
        context.getSession().getPersistenceContext().addNullProperty(
            ownerEntityKey,
            fetchedType.getPropertyName()
        );
      }
    }
  }

  private void loadFromResultSet(
      ResultSet resultSet,
      ResultSetProcessingContext context,
      Object entityInstance,
      String concreteEntityTypeName,
      EntityKey entityKey,
      LockMode lockModeToAcquire) {
    final Serializable id = entityKey.getIdentifier();

    // Get the persister for the _subclass_
    final Loadable concreteEntityPersister = (Loadable) context.getSession().getFactory().getEntityPersister( concreteEntityTypeName );

    if ( log.isTraceEnabled() ) {
      log.tracev(
          "Initializing object from ResultSet: {0}",
          MessageHelper.infoString(
              concreteEntityPersister,
              id,
              context.getSession().getFactory()
          )
      );
    }

    // add temp entry so that the next step is circular-reference
    // safe - only needed because some types don't take proper
    // advantage of two-phase-load (esp. components)
    TwoPhaseLoad.addUninitializedEntity(
        entityKey,
        entityInstance,
        concreteEntityPersister,
        lockModeToAcquire,
        !context.getLoadPlan().areLazyAttributesForceFetched(),
        context.getSession()
    );

    final EntityPersister rootEntityPersister = context.getSession().getFactory().getEntityPersister(
        concreteEntityPersister.getRootEntityName()
    );
    final Object[] values;
    try {
      values = concreteEntityPersister.hydrate(
          resultSet,
          id,
          entityInstance,
          (Loadable) entityReference.getEntityPersister(),
          concreteEntityPersister == rootEntityPersister
              ? entityReferenceAliases.getColumnAliases().getSuffixedPropertyAliases()
              : entityReferenceAliases.getColumnAliases().getSuffixedPropertyAliases( concreteEntityPersister ),
          context.getLoadPlan().areLazyAttributesForceFetched(),
          context.getSession()
      );

      context.getProcessingState( entityReference ).registerHydratedState( values );
    }
    catch (SQLException e) {
      throw context.getSession().getFactory().getJdbcServices().getSqlExceptionHelper().convert(
          e,
          "Could not read entity state from ResultSet : " + entityKey
      );
    }

    final Object rowId;
    try {
      rowId = concreteEntityPersister.hasRowId()
          ? resultSet.getObject( entityReferenceAliases.getColumnAliases().getRowIdAlias() )
          : null;
    }
    catch (SQLException e) {
      throw context.getSession().getFactory().getJdbcServices().getSqlExceptionHelper().convert(
          e,
          "Could not read entity row-id from ResultSet : " + entityKey
      );
    }

    final EntityType entityType = EntityFetch.class.isInstance( entityReference )
        ? ( (EntityFetch) entityReference ).getFetchedType()
        : entityReference.getEntityPersister().getEntityMetamodel().getEntityType();

    if ( entityType != null ) {
      String ukName = entityType.getRHSUniqueKeyPropertyName();
      if ( ukName != null ) {
        final int index = ( (UniqueKeyLoadable) concreteEntityPersister ).getPropertyIndex( ukName );
        final Type type = concreteEntityPersister.getPropertyTypes()[index];

        // polymorphism not really handled completely correctly,
        // perhaps...well, actually its ok, assuming that the
        // entity name used in the lookup is the same as the
        // the one used here, which it will be

        EntityUniqueKey euk = new EntityUniqueKey(
            entityReference.getEntityPersister().getEntityName(),
            ukName,
            type.semiResolve( values[index], context.getSession(), entityInstance ),
            type,
            concreteEntityPersister.getEntityMode(),
            context.getSession().getFactory()
        );
        context.getSession().getPersistenceContext().addEntity( euk, entityInstance );
      }
    }

    TwoPhaseLoad.postHydrate(
        concreteEntityPersister,
        id,
        values,
        rowId,
        entityInstance,
        lockModeToAcquire,
        !context.getLoadPlan().areLazyAttributesForceFetched(),
        context.getSession()
    );

    context.registerHydratedEntity( entityReference, entityKey, entityInstance );
  }

  private String getConcreteEntityTypeName(
      ResultSet resultSet,
      ResultSetProcessingContext context,
      EntityKey entityKey) {
    final Loadable loadable = (Loadable) entityReference.getEntityPersister();
    if ( ! loadable.hasSubclasses() ) {
      return entityReference.getEntityPersister().getEntityName();
    }

    final Object discriminatorValue;
    try {
      discriminatorValue = loadable.getDiscriminatorType().nullSafeGet(
          resultSet,
          entityReferenceAliases.getColumnAliases().getSuffixedDiscriminatorAlias(),
          context.getSession(),
          null
      );
    }
    catch (SQLException e) {
      throw context.getSession().getFactory().getJdbcServices().getSqlExceptionHelper().convert(
          e,
          "Could not read discriminator value from ResultSet"
      );
    }

    final String result = loadable.getSubclassForDiscriminatorValue( discriminatorValue );

    if ( result == null ) {
      // whoops! we got an instance of another class hierarchy branch
      throw new WrongClassException(
          "Discriminator: " + discriminatorValue,
          entityKey.getIdentifier(),
          entityReference.getEntityPersister().getEntityName()
      );
    }

    return result;
  }

  private void checkVersion(
      ResultSet resultSet,
      ResultSetProcessingContext context,
      EntityKey entityKey,
      Object existing) {
    final LockMode requestedLockMode = context.resolveLockMode( entityReference );
    if ( requestedLockMode != LockMode.NONE ) {
      final LockMode currentLockMode = context.getSession().getPersistenceContext().getEntry( existing ).getLockMode();
      final boolean isVersionCheckNeeded = entityReference.getEntityPersister().isVersioned()
          && currentLockMode.lessThan( requestedLockMode );

      // we don't need to worry about existing version being uninitialized because this block isn't called
      // by a re-entrant load (re-entrant loads *always* have lock mode NONE)
      if ( isVersionCheckNeeded ) {
        //we only check the version when *upgrading* lock modes
        checkVersion(
            context.getSession(),
            resultSet,
            entityReference.getEntityPersister(),
            entityReferenceAliases.getColumnAliases(),
            entityKey,
            existing
        );
        //we need to upgrade the lock mode to the mode requested
        context.getSession().getPersistenceContext().getEntry( existing ).setLockMode( requestedLockMode );
      }
    }
  }

  private void checkVersion(
      SessionImplementor session,
      ResultSet resultSet,
      EntityPersister persister,
      EntityAliases entityAliases,
      EntityKey entityKey,
      Object entityInstance) {
    final Object version = session.getPersistenceContext().getEntry( entityInstance ).getVersion();

    if ( version != null ) {
      //null version means the object is in the process of being loaded somewhere else in the ResultSet
      VersionType versionType = persister.getVersionType();
      final Object currentVersion;
      try {
        currentVersion = versionType.nullSafeGet(
            resultSet,
            entityAliases.getSuffixedVersionAliases(),
            session,
            null
        );
      }
      catch (SQLException e) {
        throw session.getFactory().getJdbcServices().getSqlExceptionHelper().convert(
            e,
            "Could not read version value from result set"
        );
      }

      if ( !versionType.isEqual( version, currentVersion ) ) {
        if ( session.getFactory().getStatistics().isStatisticsEnabled() ) {
          session.getFactory().getStatisticsImplementor().optimisticFailure( persister.getEntityName() );
        }
        throw new StaleObjectStateException( persister.getEntityName(), entityKey.getIdentifier() );
      }
    }
  }

  @Override
  public void finishUpRow(ResultSet resultSet, ResultSetProcessingContextImpl context) {
    // cant remember exactly what I was thinking here.  Maybe managing the row value caching stuff that is currently
    // done in ResultSetProcessingContextImpl.finishUpRow()
    //
    // anything else?
  }
}
TOP

Related Classes of org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.