/** @author Steve Ebersole */
public class ResourceRegistryStandardImpl implements ResourceRegistry {
  private static final CoreMessageLogger log =
      CoreLogging.messageLogger(ResourceRegistryStandardImpl.class);

  private final Map<Statement, Set<ResultSet>> xref = new HashMap<Statement, Set<ResultSet>>();
  private final Set<ResultSet> unassociatedResultSets = new HashSet<ResultSet>();

  private List<Blob> blobs;
  private List<Clob> clobs;
  private List<NClob> nclobs;

  private Statement lastQuery;

  @Override
  public boolean hasRegisteredResources() {
    return hasRegistered(xref)
        || hasRegistered(unassociatedResultSets)
        || hasRegistered(blobs)
        || hasRegistered(clobs)
        || hasRegistered(nclobs);
  }

  @Override
  public void register(Statement statement, boolean cancelable) {
    log.tracef("Registering statement [%s]", statement);
    if (xref.containsKey(statement)) {
      throw new HibernateException("JDBC Statement already registered");
    }
    xref.put(statement, null);

    if (cancelable) {
      lastQuery = statement;
    }
  }

  @Override
  public void release(Statement statement) {
    log.tracev("Releasing statement [{0}]", statement);

    // Keep this at DEBUG level, rather than warn.  Numerous connection pool implementations can
    // return a
    // proxy/wrapper around the JDBC Statement, causing excessive logging here.  See HHH-8210.
    if (log.isDebugEnabled() && !xref.containsKey(statement)) {
      log.unregisteredStatement();
    } else {
      final Set<ResultSet> resultSets = xref.get(statement);
      if (resultSets != null) {
        closeAll(resultSets);
      }
      xref.remove(statement);
    }
    close(statement);

    if (lastQuery == statement) {
      lastQuery = null;
    }
  }

  @Override
  public void release(ResultSet resultSet, Statement statement) {
    log.tracef("Releasing result set [%s]", resultSet);

    if (statement == null) {
      try {
        statement = resultSet.getStatement();
      } catch (SQLException e) {
        throw convert(e, "unable to access Statement from ResultSet");
      }
    }
    if (statement != null) {
      final Set<ResultSet> resultSets = xref.get(statement);
      if (resultSets == null) {
        log.unregisteredStatement();
      } else {
        resultSets.remove(resultSet);
        if (resultSets.isEmpty()) {
          xref.remove(statement);
        }
      }
    } else {
      final boolean removed = unassociatedResultSets.remove(resultSet);
      if (!removed) {
        log.unregisteredResultSetWithoutStatement();
      }
    }
    close(resultSet);
  }

  protected void closeAll(Set<ResultSet> resultSets) {
    for (ResultSet resultSet : resultSets) {
      close(resultSet);
    }
    resultSets.clear();
  }

  @SuppressWarnings({"unchecked"})
  public static void close(ResultSet resultSet) {
    log.tracef("Closing result set [%s]", resultSet);

    try {
      resultSet.close();
    } catch (SQLException e) {
      log.debugf("Unable to release JDBC result set [%s]", e.getMessage());
    } catch (Exception e) {
      // try to handle general errors more elegantly
      log.debugf("Unable to release JDBC result set [%s]", e.getMessage());
    }
  }

  @SuppressWarnings({"unchecked"})
  public static void close(Statement statement) {
    log.tracef("Closing prepared statement [%s]", statement);

    try {
      // if we are unable to "clean" the prepared statement,
      // we do not close it
      try {
        if (statement.getMaxRows() != 0) {
          statement.setMaxRows(0);
        }
        if (statement.getQueryTimeout() != 0) {
          statement.setQueryTimeout(0);
        }
      } catch (SQLException sqle) {
        // there was a problem "cleaning" the prepared statement
        if (log.isDebugEnabled()) {
          log.debugf("Exception clearing maxRows/queryTimeout [%s]", sqle.getMessage());
        }
        // EARLY EXIT!!!
        return;
      }
      statement.close();
    } catch (SQLException e) {
      log.debugf("Unable to release JDBC statement [%s]", e.getMessage());
    } catch (Exception e) {
      // try to handle general errors more elegantly
      log.debugf("Unable to release JDBC statement [%s]", e.getMessage());
    }
  }

  @Override
  public void register(ResultSet resultSet, Statement statement) {
    log.tracef("Registering result set [%s]", resultSet);

    if (statement == null) {
      try {
        statement = resultSet.getStatement();
      } catch (SQLException e) {
        throw convert(e, "unable to access Statement from ResultSet");
      }
    }
    if (statement != null) {
      // Keep this at DEBUG level, rather than warn.  Numerous connection pool implementations can
      // return a
      // proxy/wrapper around the JDBC Statement, causing excessive logging here.  See HHH-8210.
      if (log.isDebugEnabled() && !xref.containsKey(statement)) {
        log.debug("ResultSet statement was not registered (on register)");
      }
      Set<ResultSet> resultSets = xref.get(statement);
      if (resultSets == null) {
        resultSets = new HashSet<ResultSet>();
        xref.put(statement, resultSets);
      }
      resultSets.add(resultSet);
    } else {
      unassociatedResultSets.add(resultSet);
    }
  }

  private JDBCException convert(SQLException e, String s) {
    // todo : implement
    return null;
  }

  @Override
  public void register(Blob blob) {
    if (blobs == null) {
      blobs = new ArrayList<Blob>();
    }

    blobs.add(blob);
  }

  @Override
  public void release(Blob blob) {
    if (blobs == null) {
      log.debug("Request to release Blob, but appears no Blobs have ever been registered");
      return;
    }
    blobs.remove(blob);
  }

  @Override
  public void register(Clob clob) {
    if (clobs == null) {
      clobs = new ArrayList<Clob>();
    }
    clobs.add(clob);
  }

  @Override
  public void release(Clob clob) {
    if (clobs == null) {
      log.debug("Request to release Clob, but appears no Clobs have ever been registered");
      return;
    }
    clobs.remove(clob);
  }

  @Override
  public void register(NClob nclob) {
    // todo : just store them in clobs?
    if (nclobs == null) {
      nclobs = new ArrayList<NClob>();
    }
    nclobs.add(nclob);
  }

  @Override
  public void release(NClob nclob) {
    // todo : just store them in clobs?
    if (nclobs == null) {
      log.debug("Request to release NClob, but appears no NClobs have ever been registered");
      return;
    }
    nclobs.remove(nclob);
  }

  @Override
  public void cancelLastQuery() {
    try {
      if (lastQuery != null) {
        lastQuery.cancel();
      }
    } catch (SQLException e) {
      throw convert(e, "Cannot cancel query");
    } finally {
      lastQuery = null;
    }
  }

  @Override
  public void releaseResources() {
    log.trace("Releasing JDBC resources");

    for (Map.Entry<Statement, Set<ResultSet>> entry : xref.entrySet()) {
      if (entry.getValue() != null) {
        closeAll(entry.getValue());
      }
      close(entry.getKey());
    }
    xref.clear();

    closeAll(unassociatedResultSets);

    if (blobs != null) {
      for (Blob blob : blobs) {
        try {
          blob.free();
        } catch (SQLException e) {
          log.debugf("Unable to free JDBC Blob reference [%s]", e.getMessage());
        }
      }
      blobs.clear();
    }

    if (clobs != null) {
      for (Clob clob : clobs) {
        try {
          clob.free();
        } catch (SQLException e) {
          log.debugf("Unable to free JDBC Clob reference [%s]", e.getMessage());
        }
      }
      clobs.clear();
    }

    if (nclobs != null) {
      for (NClob nclob : nclobs) {
        try {
          nclob.free();
        } catch (SQLException e) {
          log.debugf("Unable to free JDBC NClob reference [%s]", e.getMessage());
        }
      }
      nclobs.clear();
    }
  }

  private boolean hasRegistered(Map resource) {
    return resource != null && !resource.isEmpty();
  }

  private boolean hasRegistered(Collection resource) {
    return resource != null && !resource.isEmpty();
  }
}
/**
 * An abstract {@link CollectionInitializer} implementation based on using LoadPlans
 *
 * @author Gail Badner
 */
public abstract class AbstractLoadPlanBasedCollectionInitializer extends AbstractLoadPlanBasedLoader
    implements CollectionInitializer {
  private static final CoreMessageLogger log =
      CoreLogging.messageLogger(AbstractLoadPlanBasedCollectionInitializer.class);

  private final QueryableCollection collectionPersister;
  private final LoadQueryDetails staticLoadQuery;

  public AbstractLoadPlanBasedCollectionInitializer(
      QueryableCollection collectionPersister, QueryBuildingParameters buildingParameters) {
    super(collectionPersister.getFactory());
    this.collectionPersister = collectionPersister;

    final FetchStyleLoadPlanBuildingAssociationVisitationStrategy strategy =
        new FetchStyleLoadPlanBuildingAssociationVisitationStrategy(
            collectionPersister.getFactory(),
            buildingParameters.getQueryInfluencers(),
            buildingParameters.getLockMode() != null
                ? buildingParameters.getLockMode()
                : buildingParameters.getLockOptions().getLockMode());

    final LoadPlan plan =
        MetamodelDrivenLoadPlanBuilder.buildRootCollectionLoadPlan(strategy, collectionPersister);
    this.staticLoadQuery =
        BatchingLoadQueryDetailsFactory.INSTANCE.makeCollectionLoadQueryDetails(
            collectionPersister, plan, buildingParameters);
  }

  @Override
  public void initialize(Serializable id, SessionImplementor session) throws HibernateException {
    if (log.isDebugEnabled()) {
      log.debugf(
          "Loading collection: %s",
          MessageHelper.collectionInfoString(collectionPersister, id, getFactory()));
    }

    final Serializable[] ids = new Serializable[] {id};
    try {
      final QueryParameters qp = new QueryParameters();
      qp.setPositionalParameterTypes(new Type[] {collectionPersister.getKeyType()});
      qp.setPositionalParameterValues(ids);
      qp.setCollectionKeys(ids);

      executeLoad(session, qp, staticLoadQuery, true, null);

    } catch (SQLException sqle) {
      throw getFactory()
          .getSQLExceptionHelper()
          .convert(
              sqle,
              "could not initialize a collection: "
                  + MessageHelper.collectionInfoString(collectionPersister, id, getFactory()),
              staticLoadQuery.getSqlStatement());
    }

    log.debug("Done loading collection");
  }

  protected QueryableCollection collectionPersister() {
    return collectionPersister;
  }

  @Override
  protected LoadQueryDetails getStaticLoadQuery() {
    return staticLoadQuery;
  }

  @Override
  protected int[] getNamedParameterLocs(String name) {
    throw new AssertionFailure("no named parameters");
  }

  @Override
  protected void autoDiscoverTypes(ResultSet rs) {
    throw new AssertionFailure("Auto discover types not supported in this loader");
  }
}
Exemplo n.º 3
0
/**
 * Any value that maps to columns.
 *
 * @author Gavin King
 */
public class SimpleValue implements KeyValue {
  private static final CoreMessageLogger log = CoreLogging.messageLogger(SimpleValue.class);

  public static final String DEFAULT_ID_GEN_STRATEGY = "assigned";

  private final MetadataImplementor metadata;

  private final List<Selectable> columns = new ArrayList<Selectable>();

  private String typeName;
  private Properties typeParameters;
  private boolean isNationalized;

  private Properties identifierGeneratorProperties;
  private String identifierGeneratorStrategy = DEFAULT_ID_GEN_STRATEGY;
  private String nullValue;
  private Table table;
  private String foreignKeyName;
  private boolean alternateUniqueKey;
  private boolean cascadeDeleteEnabled;

  private AttributeConverterDefinition attributeConverterDefinition;
  private Type type;

  public SimpleValue(MetadataImplementor metadata) {
    this.metadata = metadata;
  }

  public SimpleValue(MetadataImplementor metadata, Table table) {
    this(metadata);
    this.table = table;
  }

  public MetadataImplementor getMetadata() {
    return metadata;
  }

  @Override
  public ServiceRegistry getServiceRegistry() {
    return getMetadata().getMetadataBuildingOptions().getServiceRegistry();
  }

  @Override
  public boolean isCascadeDeleteEnabled() {
    return cascadeDeleteEnabled;
  }

  public void setCascadeDeleteEnabled(boolean cascadeDeleteEnabled) {
    this.cascadeDeleteEnabled = cascadeDeleteEnabled;
  }

  public void addColumn(Column column) {
    if (!columns.contains(column)) {
      columns.add(column);
    }
    column.setValue(this);
    column.setTypeIndex(columns.size() - 1);
  }

  public void addFormula(Formula formula) {
    columns.add(formula);
  }

  @Override
  public boolean hasFormula() {
    Iterator iter = getColumnIterator();
    while (iter.hasNext()) {
      Object o = iter.next();
      if (o instanceof Formula) {
        return true;
      }
    }
    return false;
  }

  @Override
  public int getColumnSpan() {
    return columns.size();
  }

  @Override
  public Iterator<Selectable> getColumnIterator() {
    return columns.iterator();
  }

  public List getConstraintColumns() {
    return columns;
  }

  public String getTypeName() {
    return typeName;
  }

  public void setTypeName(String typeName) {
    if (typeName != null && typeName.startsWith(AttributeConverterTypeAdapter.NAME_PREFIX)) {
      final String converterClassName =
          typeName.substring(AttributeConverterTypeAdapter.NAME_PREFIX.length());
      final ClassLoaderService cls =
          getMetadata()
              .getMetadataBuildingOptions()
              .getServiceRegistry()
              .getService(ClassLoaderService.class);
      try {
        final Class<AttributeConverter> converterClass = cls.classForName(converterClassName);
        attributeConverterDefinition =
            new AttributeConverterDefinition(converterClass.newInstance(), false);
        return;
      } catch (Exception e) {
        log.logBadHbmAttributeConverterType(typeName, e.getMessage());
      }
    }

    this.typeName = typeName;
  }

  public void makeNationalized() {
    this.isNationalized = true;
  }

  public boolean isNationalized() {
    return isNationalized;
  }

  public void setTable(Table table) {
    this.table = table;
  }

  @Override
  public void createForeignKey() throws MappingException {}

  @Override
  public void createForeignKeyOfEntity(String entityName) {
    if (!hasFormula() && !"none".equals(getForeignKeyName())) {
      ForeignKey fk =
          table.createForeignKey(getForeignKeyName(), getConstraintColumns(), entityName);
      fk.setCascadeDeleteEnabled(cascadeDeleteEnabled);
    }
  }

  private IdentifierGenerator identifierGenerator;

  @Override
  public IdentifierGenerator createIdentifierGenerator(
      IdentifierGeneratorFactory identifierGeneratorFactory,
      Dialect dialect,
      String defaultCatalog,
      String defaultSchema,
      RootClass rootClass)
      throws MappingException {

    if (identifierGenerator != null) {
      return identifierGenerator;
    }

    Properties params = new Properties();

    // if the hibernate-mapping did not specify a schema/catalog, use the defaults
    // specified by properties - but note that if the schema/catalog were specified
    // in hibernate-mapping, or as params, they will already be initialized and
    // will override the values set here (they are in identifierGeneratorProperties)
    if (defaultSchema != null) {
      params.setProperty(PersistentIdentifierGenerator.SCHEMA, defaultSchema);
    }
    if (defaultCatalog != null) {
      params.setProperty(PersistentIdentifierGenerator.CATALOG, defaultCatalog);
    }

    // pass the entity-name, if not a collection-id
    if (rootClass != null) {
      params.setProperty(IdentifierGenerator.ENTITY_NAME, rootClass.getEntityName());
      params.setProperty(IdentifierGenerator.JPA_ENTITY_NAME, rootClass.getJpaEntityName());
    }

    // init the table here instead of earlier, so that we can get a quoted table name
    // TODO: would it be better to simply pass the qualified table name, instead of
    //      splitting it up into schema/catalog/table names
    String tableName = getTable().getQuotedName(dialect);
    params.setProperty(PersistentIdentifierGenerator.TABLE, tableName);

    // pass the column name (a generated id almost always has a single column)
    String columnName = ((Column) getColumnIterator().next()).getQuotedName(dialect);
    params.setProperty(PersistentIdentifierGenerator.PK, columnName);

    if (rootClass != null) {
      StringBuilder tables = new StringBuilder();
      Iterator iter = rootClass.getIdentityTables().iterator();
      while (iter.hasNext()) {
        Table table = (Table) iter.next();
        tables.append(table.getQuotedName(dialect));
        if (iter.hasNext()) {
          tables.append(", ");
        }
      }
      params.setProperty(PersistentIdentifierGenerator.TABLES, tables.toString());
    } else {
      params.setProperty(PersistentIdentifierGenerator.TABLES, tableName);
    }

    if (identifierGeneratorProperties != null) {
      params.putAll(identifierGeneratorProperties);
    }

    // TODO : we should pass along all settings once "config lifecycle" is hashed out...
    final ConfigurationService cs =
        metadata
            .getMetadataBuildingOptions()
            .getServiceRegistry()
            .getService(ConfigurationService.class);

    params.put(
        AvailableSettings.PREFER_POOLED_VALUES_LO,
        cs.getSetting(
            AvailableSettings.PREFER_POOLED_VALUES_LO, StandardConverters.BOOLEAN, false));
    if (cs.getSettings().get(AvailableSettings.PREFERRED_POOLED_OPTIMIZER) != null) {
      params.put(
          AvailableSettings.PREFERRED_POOLED_OPTIMIZER,
          cs.getSettings().get(AvailableSettings.PREFERRED_POOLED_OPTIMIZER));
    }

    identifierGeneratorFactory.setDialect(dialect);
    identifierGenerator =
        identifierGeneratorFactory.createIdentifierGenerator(
            identifierGeneratorStrategy, getType(), params);

    return identifierGenerator;
  }

  public boolean isUpdateable() {
    // needed to satisfy KeyValue
    return true;
  }

  public FetchMode getFetchMode() {
    return FetchMode.SELECT;
  }

  public Properties getIdentifierGeneratorProperties() {
    return identifierGeneratorProperties;
  }

  public String getNullValue() {
    return nullValue;
  }

  public Table getTable() {
    return table;
  }

  /**
   * Returns the identifierGeneratorStrategy.
   *
   * @return String
   */
  public String getIdentifierGeneratorStrategy() {
    return identifierGeneratorStrategy;
  }

  public boolean isIdentityColumn(
      IdentifierGeneratorFactory identifierGeneratorFactory, Dialect dialect) {
    identifierGeneratorFactory.setDialect(dialect);
    return identifierGeneratorFactory
        .getIdentifierGeneratorClass(identifierGeneratorStrategy)
        .equals(IdentityGenerator.class);
  }

  /**
   * Sets the identifierGeneratorProperties.
   *
   * @param identifierGeneratorProperties The identifierGeneratorProperties to set
   */
  public void setIdentifierGeneratorProperties(Properties identifierGeneratorProperties) {
    this.identifierGeneratorProperties = identifierGeneratorProperties;
  }

  /**
   * Sets the identifierGeneratorStrategy.
   *
   * @param identifierGeneratorStrategy The identifierGeneratorStrategy to set
   */
  public void setIdentifierGeneratorStrategy(String identifierGeneratorStrategy) {
    this.identifierGeneratorStrategy = identifierGeneratorStrategy;
  }

  /**
   * Sets the nullValue.
   *
   * @param nullValue The nullValue to set
   */
  public void setNullValue(String nullValue) {
    this.nullValue = nullValue;
  }

  public String getForeignKeyName() {
    return foreignKeyName;
  }

  public void setForeignKeyName(String foreignKeyName) {
    this.foreignKeyName = foreignKeyName;
  }

  public boolean isAlternateUniqueKey() {
    return alternateUniqueKey;
  }

  public void setAlternateUniqueKey(boolean unique) {
    this.alternateUniqueKey = unique;
  }

  public boolean isNullable() {
    Iterator itr = getColumnIterator();
    while (itr.hasNext()) {
      final Object selectable = itr.next();
      if (selectable instanceof Formula) {
        // if there are *any* formulas, then the Value overall is
        // considered nullable
        return true;
      } else if (!((Column) selectable).isNullable()) {
        // if there is a single non-nullable column, the Value
        // overall is considered non-nullable.
        return false;
      }
    }
    // nullable by default
    return true;
  }

  public boolean isSimpleValue() {
    return true;
  }

  public boolean isValid(Mapping mapping) throws MappingException {
    return getColumnSpan() == getType().getColumnSpan(mapping);
  }

  public Type getType() throws MappingException {
    if (type != null) {
      return type;
    }

    if (typeName == null) {
      throw new MappingException("No type name");
    }

    if (typeParameters != null
        && Boolean.valueOf(typeParameters.getProperty(DynamicParameterizedType.IS_DYNAMIC))
        && typeParameters.get(DynamicParameterizedType.PARAMETER_TYPE) == null) {
      createParameterImpl();
    }

    Type result = metadata.getTypeResolver().heuristicType(typeName, typeParameters);
    if (result == null) {
      String msg = "Could not determine type for: " + typeName;
      if (table != null) {
        msg += ", at table: " + table.getName();
      }
      if (columns != null && columns.size() > 0) {
        msg += ", for columns: " + columns;
      }
      throw new MappingException(msg);
    }

    return result;
  }

  public void setTypeUsingReflection(String className, String propertyName)
      throws MappingException {
    // NOTE : this is called as the last piece in setting SimpleValue type information, and
    // implementations
    // rely on that fact, using it as a signal that all information it is going to get is defined at
    // this point...

    if (typeName != null) {
      // assume either (a) explicit type was specified or (b) determine was already performed
      return;
    }

    if (type != null) {
      return;
    }

    if (attributeConverterDefinition == null) {
      // this is here to work like legacy.  This should change when we integrate with metamodel to
      // look for SqlTypeDescriptor and JavaTypeDescriptor individually and create the BasicType
      // (well, really
      // keep a registry of [SqlTypeDescriptor,JavaTypeDescriptor] -> BasicType...)
      if (className == null) {
        throw new MappingException(
            "Attribute types for a dynamic entity must be explicitly specified: " + propertyName);
      }
      typeName =
          ReflectHelper.reflectedPropertyClass(
                  className,
                  propertyName,
                  metadata
                      .getMetadataBuildingOptions()
                      .getServiceRegistry()
                      .getService(ClassLoaderService.class))
              .getName();
      // todo : to fully support isNationalized here we need do the process hinted at above
      // 		essentially, much of the logic from #buildAttributeConverterTypeAdapter wrt resolving
      //		a (1) SqlTypeDescriptor, a (2) JavaTypeDescriptor and dynamically building a BasicType
      // 		combining them.
      return;
    }

    // we had an AttributeConverter...
    type = buildAttributeConverterTypeAdapter();
  }

  /**
   * Build a Hibernate Type that incorporates the JPA AttributeConverter. AttributeConverter works
   * totally in memory, meaning it converts between one Java representation (the entity attribute
   * representation) and another (the value bound into JDBC statements or extracted from results).
   * However, the Hibernate Type system operates at the lower level of actually dealing directly
   * with those JDBC objects. So even though we have an AttributeConverter, we still need to "fill
   * out" the rest of the BasicType data and bridge calls to bind/extract through the converter.
   *
   * <p>Essentially the idea here is that an intermediate Java type needs to be used. Let's use an
   * example as a means to illustrate... Consider an {@code AttributeConverter<Integer,String>}.
   * This tells Hibernate that the domain model defines this attribute as an Integer value (the
   * 'entityAttributeJavaType'), but that we need to treat the value as a String (the
   * 'databaseColumnJavaType') when dealing with JDBC (aka, the database type is a VARCHAR/CHAR):
   *
   * <ul>
   *   <li>When binding values to PreparedStatements we need to convert the Integer value from the
   *       entity into a String and pass that String to setString. The conversion is handled by
   *       calling {@link AttributeConverter#convertToDatabaseColumn(Object)}
   *   <li>When extracting values from ResultSets (or CallableStatement parameters) we need to
   *       handle the value via getString, and convert that returned String to an Integer. That
   *       conversion is handled by calling {@link
   *       AttributeConverter#convertToEntityAttribute(Object)}
   * </ul>
   *
   * @return The built AttributeConverter -> Type adapter
   * @todo : ultimately I want to see attributeConverterJavaType and attributeConverterJdbcTypeCode
   *     specify-able separately then we can "play them against each other" in terms of determining
   *     proper typing
   * @todo : see if we already have previously built a custom on-the-fly BasicType for this
   *     AttributeConverter; see note below about caching
   */
  @SuppressWarnings("unchecked")
  private Type buildAttributeConverterTypeAdapter() {
    // todo : validate the number of columns present here?

    final Class entityAttributeJavaType = attributeConverterDefinition.getEntityAttributeType();
    final Class databaseColumnJavaType = attributeConverterDefinition.getDatabaseColumnType();

    // resolve the JavaTypeDescriptor
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // For the JavaTypeDescriptor portion we simply resolve the "entity attribute representation"
    // part of
    // the AttributeConverter to resolve the corresponding descriptor.
    final JavaTypeDescriptor entityAttributeJavaTypeDescriptor =
        JavaTypeDescriptorRegistry.INSTANCE.getDescriptor(entityAttributeJavaType);

    // build the SqlTypeDescriptor adapter
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // Going back to the illustration, this should be a SqlTypeDescriptor that handles the Integer
    // <-> String
    //		conversions.  This is the more complicated piece.  First we need to determine the JDBC type
    // code
    //		corresponding to the AttributeConverter's declared "databaseColumnJavaType" (how we read
    // that value out
    // 		of ResultSets).  See JdbcTypeJavaClassMappings for details.  Again, given example, this
    // should return
    // 		VARCHAR/CHAR
    int jdbcTypeCode =
        JdbcTypeJavaClassMappings.INSTANCE.determineJdbcTypeCodeForJavaClass(
            databaseColumnJavaType);
    if (isNationalized()) {
      jdbcTypeCode =
          NationalizedTypeMappings.INSTANCE.getCorrespondingNationalizedCode(jdbcTypeCode);
    }
    // find the standard SqlTypeDescriptor for that JDBC type code.
    final SqlTypeDescriptor sqlTypeDescriptor =
        SqlTypeDescriptorRegistry.INSTANCE.getDescriptor(jdbcTypeCode);
    // find the JavaTypeDescriptor representing the "intermediate database type representation".
    // Back to the
    // 		illustration, this should be the type descriptor for Strings
    final JavaTypeDescriptor intermediateJavaTypeDescriptor =
        JavaTypeDescriptorRegistry.INSTANCE.getDescriptor(databaseColumnJavaType);
    // and finally construct the adapter, which injects the AttributeConverter calls into the
    // binding/extraction
    // 		process...
    final SqlTypeDescriptor sqlTypeDescriptorAdapter =
        new AttributeConverterSqlTypeDescriptorAdapter(
            attributeConverterDefinition.getAttributeConverter(),
            sqlTypeDescriptor,
            intermediateJavaTypeDescriptor);

    // todo : cache the AttributeConverterTypeAdapter in case that AttributeConverter is applied
    // multiple times.

    final String name =
        AttributeConverterTypeAdapter.NAME_PREFIX
            + attributeConverterDefinition.getAttributeConverter().getClass().getName();
    final String description =
        String.format(
            "BasicType adapter for AttributeConverter<%s,%s>",
            entityAttributeJavaType.getSimpleName(), databaseColumnJavaType.getSimpleName());
    return new AttributeConverterTypeAdapter(
        name,
        description,
        attributeConverterDefinition.getAttributeConverter(),
        sqlTypeDescriptorAdapter,
        entityAttributeJavaType,
        databaseColumnJavaType,
        entityAttributeJavaTypeDescriptor);
  }

  public boolean isTypeSpecified() {
    return typeName != null;
  }

  public void setTypeParameters(Properties parameterMap) {
    this.typeParameters = parameterMap;
  }

  public Properties getTypeParameters() {
    return typeParameters;
  }

  public void copyTypeFrom(SimpleValue sourceValue) {
    setTypeName(sourceValue.getTypeName());
    setTypeParameters(sourceValue.getTypeParameters());

    type = sourceValue.type;
    attributeConverterDefinition = sourceValue.attributeConverterDefinition;
  }

  @Override
  public String toString() {
    return getClass().getName() + '(' + columns.toString() + ')';
  }

  public Object accept(ValueVisitor visitor) {
    return visitor.accept(this);
  }

  public boolean[] getColumnInsertability() {
    boolean[] result = new boolean[getColumnSpan()];
    int i = 0;
    Iterator iter = getColumnIterator();
    while (iter.hasNext()) {
      Selectable s = (Selectable) iter.next();
      result[i++] = !s.isFormula();
    }
    return result;
  }

  public boolean[] getColumnUpdateability() {
    return getColumnInsertability();
  }

  public void setJpaAttributeConverterDefinition(
      AttributeConverterDefinition attributeConverterDefinition) {
    this.attributeConverterDefinition = attributeConverterDefinition;
  }

  private void createParameterImpl() {
    try {
      String[] columnsNames = new String[columns.size()];
      for (int i = 0; i < columns.size(); i++) {
        Selectable column = columns.get(i);
        if (column instanceof Column) {
          columnsNames[i] = ((Column) column).getName();
        }
      }

      final XProperty xProperty =
          (XProperty) typeParameters.get(DynamicParameterizedType.XPROPERTY);
      // todo : not sure this works for handling @MapKeyEnumerated
      final Annotation[] annotations = xProperty == null ? null : xProperty.getAnnotations();

      final ClassLoaderService classLoaderService =
          getMetadata()
              .getMetadataBuildingOptions()
              .getServiceRegistry()
              .getService(ClassLoaderService.class);
      typeParameters.put(
          DynamicParameterizedType.PARAMETER_TYPE,
          new ParameterTypeImpl(
              classLoaderService.classForName(
                  typeParameters.getProperty(DynamicParameterizedType.RETURNED_CLASS)),
              annotations,
              table.getCatalog(),
              table.getSchema(),
              table.getName(),
              Boolean.valueOf(typeParameters.getProperty(DynamicParameterizedType.IS_PRIMARY_KEY)),
              columnsNames));
    } catch (ClassLoadingException e) {
      throw new MappingException(
          "Could not create DynamicParameterizedType for type: " + typeName, e);
    }
  }

  private static final class ParameterTypeImpl implements DynamicParameterizedType.ParameterType {

    private final Class returnedClass;
    private final Annotation[] annotationsMethod;
    private final String catalog;
    private final String schema;
    private final String table;
    private final boolean primaryKey;
    private final String[] columns;

    private ParameterTypeImpl(
        Class returnedClass,
        Annotation[] annotationsMethod,
        String catalog,
        String schema,
        String table,
        boolean primaryKey,
        String[] columns) {
      this.returnedClass = returnedClass;
      this.annotationsMethod = annotationsMethod;
      this.catalog = catalog;
      this.schema = schema;
      this.table = table;
      this.primaryKey = primaryKey;
      this.columns = columns;
    }

    @Override
    public Class getReturnedClass() {
      return returnedClass;
    }

    @Override
    public Annotation[] getAnnotationsMethod() {
      return annotationsMethod;
    }

    @Override
    public String getCatalog() {
      return catalog;
    }

    @Override
    public String getSchema() {
      return schema;
    }

    @Override
    public String getTable() {
      return table;
    }

    @Override
    public boolean isPrimaryKey() {
      return primaryKey;
    }

    @Override
    public String[] getColumns() {
      return columns;
    }
  }
}
/**
 * Defines the default delete event listener used by hibernate for deleting entities from the
 * datastore in response to generated delete events.
 *
 * @author Steve Ebersole
 */
public class DefaultDeleteEventListener implements DeleteEventListener {
  private static final CoreMessageLogger LOG =
      CoreLogging.messageLogger(DefaultDeleteEventListener.class);

  /**
   * Handle the given delete event.
   *
   * @param event The delete event to be handled.
   * @throws HibernateException
   */
  public void onDelete(DeleteEvent event) throws HibernateException {
    onDelete(event, new IdentitySet());
  }

  /**
   * Handle the given delete event. This is the cascaded form.
   *
   * @param event The delete event.
   * @param transientEntities The cache of entities already deleted
   * @throws HibernateException
   */
  public void onDelete(DeleteEvent event, Set transientEntities) throws HibernateException {

    final EventSource source = event.getSession();

    final PersistenceContext persistenceContext = source.getPersistenceContext();
    Object entity = persistenceContext.unproxyAndReassociate(event.getObject());

    EntityEntry entityEntry = persistenceContext.getEntry(entity);
    final EntityPersister persister;
    final Serializable id;
    final Object version;

    if (entityEntry == null) {
      LOG.trace("Entity was not persistent in delete processing");

      persister = source.getEntityPersister(event.getEntityName(), entity);

      if (ForeignKeys.isTransient(persister.getEntityName(), entity, null, source)) {
        deleteTransientEntity(
            source, entity, event.isCascadeDeleteEnabled(), persister, transientEntities);
        // EARLY EXIT!!!
        return;
      }
      performDetachedEntityDeletionCheck(event);

      id = persister.getIdentifier(entity, source);

      if (id == null) {
        throw new TransientObjectException(
            "the detached instance passed to delete() had a null identifier");
      }

      final EntityKey key = source.generateEntityKey(id, persister);

      persistenceContext.checkUniqueness(key, entity);

      new OnUpdateVisitor(source, id, entity).process(entity, persister);

      version = persister.getVersion(entity);

      entityEntry =
          persistenceContext.addEntity(
              entity,
              (persister.isMutable() ? Status.MANAGED : Status.READ_ONLY),
              persister.getPropertyValues(entity),
              key,
              version,
              LockMode.NONE,
              true,
              persister,
              false);
    } else {
      LOG.trace("Deleting a persistent instance");

      if (entityEntry.getStatus() == Status.DELETED || entityEntry.getStatus() == Status.GONE) {
        LOG.trace("Object was already deleted");
        return;
      }
      persister = entityEntry.getPersister();
      id = entityEntry.getId();
      version = entityEntry.getVersion();
    }

    /*if ( !persister.isMutable() ) {
    	throw new HibernateException(
    			"attempted to delete an object of immutable class: " +
    			MessageHelper.infoString(persister)
    		);
    }*/

    if (invokeDeleteLifecycle(source, entity, persister)) {
      return;
    }

    deleteEntity(
        source,
        entity,
        entityEntry,
        event.isCascadeDeleteEnabled(),
        event.isOrphanRemovalBeforeUpdates(),
        persister,
        transientEntities);

    if (source.getFactory().getSettings().isIdentifierRollbackEnabled()) {
      persister.resetIdentifier(entity, id, version, source);
    }
  }

  /**
   * Called when we have recognized an attempt to delete a detached entity.
   *
   * <p>This is perfectly valid in Hibernate usage; JPA, however, forbids this. Thus, this is a hook
   * for HEM to affect this behavior.
   *
   * @param event The event.
   */
  protected void performDetachedEntityDeletionCheck(DeleteEvent event) {
    // ok in normal Hibernate usage to delete a detached entity; JPA however
    // forbids it, thus this is a hook for HEM to affect this behavior
  }

  /**
   * We encountered a delete request on a transient instance.
   *
   * <p>This is a deviation from historical Hibernate (pre-3.2) behavior to align with the JPA spec,
   * which states that transient entities can be passed to remove operation in which case cascades
   * still need to be performed.
   *
   * @param session The session which is the source of the event
   * @param entity The entity being delete processed
   * @param cascadeDeleteEnabled Is cascading of deletes enabled
   * @param persister The entity persister
   * @param transientEntities A cache of already visited transient entities (to avoid infinite
   *     recursion).
   */
  protected void deleteTransientEntity(
      EventSource session,
      Object entity,
      boolean cascadeDeleteEnabled,
      EntityPersister persister,
      Set transientEntities) {
    LOG.handlingTransientEntity();
    if (transientEntities.contains(entity)) {
      LOG.trace("Already handled transient entity; skipping");
      return;
    }
    transientEntities.add(entity);
    cascadeBeforeDelete(session, persister, entity, null, transientEntities);
    cascadeAfterDelete(session, persister, entity, transientEntities);
  }

  /**
   * Perform the entity deletion. Well, as with most operations, does not really perform it; just
   * schedules an action/execution with the {@link org.hibernate.engine.spi.ActionQueue} for
   * execution during flush.
   *
   * @param session The originating session
   * @param entity The entity to delete
   * @param entityEntry The entity's entry in the {@link PersistenceContext}
   * @param isCascadeDeleteEnabled Is delete cascading enabled?
   * @param persister The entity persister.
   * @param transientEntities A cache of already deleted entities.
   */
  protected final void deleteEntity(
      final EventSource session,
      final Object entity,
      final EntityEntry entityEntry,
      final boolean isCascadeDeleteEnabled,
      final boolean isOrphanRemovalBeforeUpdates,
      final EntityPersister persister,
      final Set transientEntities) {

    if (LOG.isTraceEnabled()) {
      LOG.tracev(
          "Deleting {0}",
          MessageHelper.infoString(persister, entityEntry.getId(), session.getFactory()));
    }

    final PersistenceContext persistenceContext = session.getPersistenceContext();
    final Type[] propTypes = persister.getPropertyTypes();
    final Object version = entityEntry.getVersion();

    final Object[] currentState;
    if (entityEntry.getLoadedState() == null) {
      // ie. the entity came in from update()
      currentState = persister.getPropertyValues(entity);
    } else {
      currentState = entityEntry.getLoadedState();
    }

    final Object[] deletedState = createDeletedState(persister, currentState, session);
    entityEntry.setDeletedState(deletedState);

    session
        .getInterceptor()
        .onDelete(
            entity, entityEntry.getId(), deletedState, persister.getPropertyNames(), propTypes);

    // before any callbacks, etc, so subdeletions see that this deletion happened first
    persistenceContext.setEntryStatus(entityEntry, Status.DELETED);
    final EntityKey key = session.generateEntityKey(entityEntry.getId(), persister);

    cascadeBeforeDelete(session, persister, entity, entityEntry, transientEntities);

    new ForeignKeys.Nullifier(entity, true, false, session)
        .nullifyTransientReferences(entityEntry.getDeletedState(), propTypes);
    new Nullability(session).checkNullability(entityEntry.getDeletedState(), persister, true);
    persistenceContext.getNullifiableEntityKeys().add(key);

    if (isOrphanRemovalBeforeUpdates) {
      // TODO: The removeOrphan concept is a temporary "hack" for HHH-6484.  This should be removed
      // once action/task
      // ordering is improved.
      session
          .getActionQueue()
          .addAction(
              new OrphanRemovalAction(
                  entityEntry.getId(),
                  deletedState,
                  version,
                  entity,
                  persister,
                  isCascadeDeleteEnabled,
                  session));
    } else {
      // Ensures that containing deletions happen before sub-deletions
      session
          .getActionQueue()
          .addAction(
              new EntityDeleteAction(
                  entityEntry.getId(),
                  deletedState,
                  version,
                  entity,
                  persister,
                  isCascadeDeleteEnabled,
                  session));
    }

    cascadeAfterDelete(session, persister, entity, transientEntities);

    // the entry will be removed after the flush, and will no longer
    // override the stale snapshot
    // This is now handled by removeEntity() in EntityDeleteAction
    // persistenceContext.removeDatabaseSnapshot(key);
  }

  private Object[] createDeletedState(
      EntityPersister persister, Object[] currentState, EventSource session) {
    Type[] propTypes = persister.getPropertyTypes();
    final Object[] deletedState = new Object[propTypes.length];
    //		TypeFactory.deepCopy( currentState, propTypes, persister.getPropertyUpdateability(),
    // deletedState, session );
    boolean[] copyability = new boolean[propTypes.length];
    java.util.Arrays.fill(copyability, true);
    TypeHelper.deepCopy(currentState, propTypes, copyability, deletedState, session);
    return deletedState;
  }

  protected boolean invokeDeleteLifecycle(
      EventSource session, Object entity, EntityPersister persister) {
    if (persister.implementsLifecycle()) {
      LOG.debug("Calling onDelete()");
      if (((Lifecycle) entity).onDelete(session)) {
        LOG.debug("Deletion vetoed by onDelete()");
        return true;
      }
    }
    return false;
  }

  protected void cascadeBeforeDelete(
      EventSource session,
      EntityPersister persister,
      Object entity,
      EntityEntry entityEntry,
      Set transientEntities)
      throws HibernateException {

    CacheMode cacheMode = session.getCacheMode();
    session.setCacheMode(CacheMode.GET);
    session.getPersistenceContext().incrementCascadeLevel();
    try {
      // cascade-delete to collections BEFORE the collection owner is deleted
      Cascade.cascade(
          CascadingActions.DELETE,
          CascadePoint.AFTER_INSERT_BEFORE_DELETE,
          session,
          persister,
          entity,
          transientEntities);
    } finally {
      session.getPersistenceContext().decrementCascadeLevel();
      session.setCacheMode(cacheMode);
    }
  }

  protected void cascadeAfterDelete(
      EventSource session, EntityPersister persister, Object entity, Set transientEntities)
      throws HibernateException {

    CacheMode cacheMode = session.getCacheMode();
    session.setCacheMode(CacheMode.GET);
    session.getPersistenceContext().incrementCascadeLevel();
    try {
      // cascade-delete to many-to-one AFTER the parent was deleted
      Cascade.cascade(
          CascadingActions.DELETE,
          CascadePoint.BEFORE_INSERT_AFTER_DELETE,
          session,
          persister,
          entity,
          transientEntities);
    } finally {
      session.getPersistenceContext().decrementCascadeLevel();
      session.setCacheMode(cacheMode);
    }
  }
}
/**
 * Defines the default load event listeners used by hibernate for loading entities in response to
 * generated load events.
 *
 * @author Eric Dalquist
 * @author Steve Ebersole
 */
public class DefaultResolveNaturalIdEventListener extends AbstractLockUpgradeEventListener
    implements ResolveNaturalIdEventListener {

  public static final Object REMOVED_ENTITY_MARKER = new Object();
  public static final Object INCONSISTENT_RTN_CLASS_MARKER = new Object();

  private static final CoreMessageLogger LOG =
      CoreLogging.messageLogger(DefaultResolveNaturalIdEventListener.class);

  @Override
  public void onResolveNaturalId(ResolveNaturalIdEvent event) throws HibernateException {
    final Serializable entityId = resolveNaturalId(event);
    event.setEntityId(entityId);
  }

  /**
   * Coordinates the efforts to load a given entity. First, an attempt is made to load the entity
   * from the session-level cache. If not found there, an attempt is made to locate it in
   * second-level cache. Lastly, an attempt is made to load it directly from the datasource.
   *
   * @param event The load event
   * @return The loaded entity, or null.
   */
  protected Serializable resolveNaturalId(final ResolveNaturalIdEvent event) {
    final EntityPersister persister = event.getEntityPersister();

    final boolean traceEnabled = LOG.isTraceEnabled();
    if (traceEnabled)
      LOG.tracev(
          "Attempting to resolve: {0}",
          MessageHelper.infoString(
              persister, event.getNaturalIdValues(), event.getSession().getFactory()));

    Serializable entityId = resolveFromCache(event);
    if (entityId != null) {
      if (traceEnabled)
        LOG.tracev(
            "Resolved object in cache: {0}",
            MessageHelper.infoString(
                persister, event.getNaturalIdValues(), event.getSession().getFactory()));
      return entityId;
    }

    if (traceEnabled)
      LOG.tracev(
          "Object not resolved in any cache: {0}",
          MessageHelper.infoString(
              persister, event.getNaturalIdValues(), event.getSession().getFactory()));

    return loadFromDatasource(event);
  }

  /**
   * Attempts to resolve the entity id corresponding to the event's natural id values from the
   * session
   *
   * @param event The load event
   * @return The entity from the cache, or null.
   */
  protected Serializable resolveFromCache(final ResolveNaturalIdEvent event) {
    return event
        .getSession()
        .getPersistenceContext()
        .getNaturalIdHelper()
        .findCachedNaturalIdResolution(
            event.getEntityPersister(), event.getOrderedNaturalIdValues());
  }

  /**
   * Performs the process of loading an entity from the configured underlying datasource.
   *
   * @param event The load event
   * @return The object loaded from the datasource, or null if not found.
   */
  protected Serializable loadFromDatasource(final ResolveNaturalIdEvent event) {
    final SessionFactoryImplementor factory = event.getSession().getFactory();
    final boolean stats = factory.getStatistics().isStatisticsEnabled();
    long startTime = 0;
    if (stats) {
      startTime = System.currentTimeMillis();
    }

    final Serializable pk =
        event
            .getEntityPersister()
            .loadEntityIdByNaturalId(
                event.getOrderedNaturalIdValues(), event.getLockOptions(), event.getSession());

    if (stats) {
      final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy =
          event.getEntityPersister().getNaturalIdCacheAccessStrategy();
      final String regionName =
          naturalIdCacheAccessStrategy == null
              ? null
              : naturalIdCacheAccessStrategy.getRegion().getName();

      factory
          .getStatisticsImplementor()
          .naturalIdQueryExecuted(regionName, System.currentTimeMillis() - startTime);
    }

    // PK can be null if the entity doesn't exist
    if (pk != null) {
      event
          .getSession()
          .getPersistenceContext()
          .getNaturalIdHelper()
          .cacheNaturalIdCrossReferenceFromLoad(
              event.getEntityPersister(), pk, event.getOrderedNaturalIdValues());
    }

    return pk;
  }
}
/**
 * A convenience bas class for listeners responding to save events.
 *
 * @author Steve Ebersole.
 */
public abstract class AbstractSaveEventListener extends AbstractReassociateEventListener {
  private static final CoreMessageLogger LOG =
      CoreLogging.messageLogger(AbstractSaveEventListener.class);

  public static enum EntityState {
    PERSISTENT,
    TRANSIENT,
    DETACHED,
    DELETED
  }

  /**
   * Prepares the save call using the given requested id.
   *
   * @param entity The entity to be saved.
   * @param requestedId The id to which to associate the entity.
   * @param entityName The name of the entity being saved.
   * @param anything Generally cascade-specific information.
   * @param source The session which is the source of this save event.
   * @return The id used to save the entity.
   */
  protected Serializable saveWithRequestedId(
      Object entity,
      Serializable requestedId,
      String entityName,
      Object anything,
      EventSource source) {
    return performSave(
        entity,
        requestedId,
        source.getEntityPersister(entityName, entity),
        false,
        anything,
        source,
        true);
  }

  /**
   * Prepares the save call using a newly generated id.
   *
   * @param entity The entity to be saved
   * @param entityName The entity-name for the entity to be saved
   * @param anything Generally cascade-specific information.
   * @param source The session which is the source of this save event.
   * @param requiresImmediateIdAccess does the event context require access to the identifier
   *     immediately after execution of this method (if not, post-insert style id generators may be
   *     postponed if we are outside a transaction).
   * @return The id used to save the entity; may be null depending on the type of id generator used
   *     and the requiresImmediateIdAccess value
   */
  protected Serializable saveWithGeneratedId(
      Object entity,
      String entityName,
      Object anything,
      EventSource source,
      boolean requiresImmediateIdAccess) {
    EntityPersister persister = source.getEntityPersister(entityName, entity);
    Serializable generatedId = persister.getIdentifierGenerator().generate(source, entity);
    if (generatedId == null) {
      throw new IdentifierGenerationException("null id generated for:" + entity.getClass());
    } else if (generatedId == IdentifierGeneratorHelper.SHORT_CIRCUIT_INDICATOR) {
      return source.getIdentifier(entity);
    } else if (generatedId == IdentifierGeneratorHelper.POST_INSERT_INDICATOR) {
      return performSave(
          entity, null, persister, true, anything, source, requiresImmediateIdAccess);
    } else {
      // TODO: define toString()s for generators
      if (LOG.isDebugEnabled()) {
        LOG.debugf(
            "Generated identifier: %s, using strategy: %s",
            persister.getIdentifierType().toLoggableString(generatedId, source.getFactory()),
            persister.getIdentifierGenerator().getClass().getName());
      }

      return performSave(entity, generatedId, persister, false, anything, source, true);
    }
  }

  /**
   * Prepares the save call by checking the session caches for a pre-existing entity and performing
   * any lifecycle callbacks.
   *
   * @param entity The entity to be saved.
   * @param id The id by which to save the entity.
   * @param persister The entity's persister instance.
   * @param useIdentityColumn Is an identity column being used?
   * @param anything Generally cascade-specific information.
   * @param source The session from which the event originated.
   * @param requiresImmediateIdAccess does the event context require access to the identifier
   *     immediately after execution of this method (if not, post-insert style id generators may be
   *     postponed if we are outside a transaction).
   * @return The id used to save the entity; may be null depending on the type of id generator used
   *     and the requiresImmediateIdAccess value
   */
  protected Serializable performSave(
      Object entity,
      Serializable id,
      EntityPersister persister,
      boolean useIdentityColumn,
      Object anything,
      EventSource source,
      boolean requiresImmediateIdAccess) {

    if (LOG.isTraceEnabled()) {
      LOG.tracev("Saving {0}", MessageHelper.infoString(persister, id, source.getFactory()));
    }

    final EntityKey key;
    if (!useIdentityColumn) {
      key = source.generateEntityKey(id, persister);
      Object old = source.getPersistenceContext().getEntity(key);
      if (old != null) {
        if (source.getPersistenceContext().getEntry(old).getStatus() == Status.DELETED) {
          source.forceFlush(source.getPersistenceContext().getEntry(old));
        } else {
          throw new NonUniqueObjectException(id, persister.getEntityName());
        }
      }
      persister.setIdentifier(entity, id, source);
    } else {
      key = null;
    }

    if (invokeSaveLifecycle(entity, persister, source)) {
      return id; // EARLY EXIT
    }

    return performSaveOrReplicate(
        entity, key, persister, useIdentityColumn, anything, source, requiresImmediateIdAccess);
  }

  protected boolean invokeSaveLifecycle(
      Object entity, EntityPersister persister, EventSource source) {
    // Sub-insertions should occur before containing insertion so
    // Try to do the callback now
    if (persister.implementsLifecycle()) {
      LOG.debug("Calling onSave()");
      if (((Lifecycle) entity).onSave(source)) {
        LOG.debug("Insertion vetoed by onSave()");
        return true;
      }
    }
    return false;
  }

  /**
   * Performs all the actual work needed to save an entity (well to get the save moved to the
   * execution queue).
   *
   * @param entity The entity to be saved
   * @param key The id to be used for saving the entity (or null, in the case of identity columns)
   * @param persister The entity's persister instance.
   * @param useIdentityColumn Should an identity column be used for id generation?
   * @param anything Generally cascade-specific information.
   * @param source The session which is the source of the current event.
   * @param requiresImmediateIdAccess Is access to the identifier required immediately after the
   *     completion of the save? persist(), for example, does not require this...
   * @return The id used to save the entity; may be null depending on the type of id generator used
   *     and the requiresImmediateIdAccess value
   */
  protected Serializable performSaveOrReplicate(
      Object entity,
      EntityKey key,
      EntityPersister persister,
      boolean useIdentityColumn,
      Object anything,
      EventSource source,
      boolean requiresImmediateIdAccess) {

    Serializable id = key == null ? null : key.getIdentifier();

    boolean inTxn = source.isTransactionInProgress();
    boolean shouldDelayIdentityInserts = !inTxn && !requiresImmediateIdAccess;

    // Put a placeholder in entries, so we don't recurse back and try to save() the
    // same object again. QUESTION: should this be done before onSave() is called?
    // likewise, should it be done before onUpdate()?
    EntityEntry original =
        source
            .getPersistenceContext()
            .addEntry(
                entity,
                Status.SAVING,
                null,
                null,
                id,
                null,
                LockMode.WRITE,
                useIdentityColumn,
                persister,
                false,
                false);

    cascadeBeforeSave(source, persister, entity, anything);

    Object[] values = persister.getPropertyValuesToInsert(entity, getMergeMap(anything), source);
    Type[] types = persister.getPropertyTypes();

    boolean substitute = substituteValuesIfNecessary(entity, id, values, persister, source);

    if (persister.hasCollections()) {
      substitute = substitute || visitCollectionsBeforeSave(entity, id, values, types, source);
    }

    if (substitute) {
      persister.setPropertyValues(entity, values);
    }

    TypeHelper.deepCopy(values, types, persister.getPropertyUpdateability(), values, source);

    AbstractEntityInsertAction insert =
        addInsertAction(
            values, id, entity, persister, useIdentityColumn, source, shouldDelayIdentityInserts);

    // postpone initializing id in case the insert has non-nullable transient dependencies
    // that are not resolved until cascadeAfterSave() is executed
    cascadeAfterSave(source, persister, entity, anything);
    if (useIdentityColumn && insert.isEarlyInsert()) {
      if (!EntityIdentityInsertAction.class.isInstance(insert)) {
        throw new IllegalStateException(
            "Insert should be using an identity column, but action is of unexpected type: "
                + insert.getClass().getName());
      }
      id = ((EntityIdentityInsertAction) insert).getGeneratedId();

      insert.handleNaturalIdPostSaveNotifications(id);
    }

    EntityEntry newEntry = source.getPersistenceContext().getEntry(entity);

    if (newEntry != original) {
      EntityEntryExtraState extraState = newEntry.getExtraState(EntityEntryExtraState.class);
      if (extraState == null) {
        newEntry.addExtraState(original.getExtraState(EntityEntryExtraState.class));
      }
    }

    return id;
  }

  private AbstractEntityInsertAction addInsertAction(
      Object[] values,
      Serializable id,
      Object entity,
      EntityPersister persister,
      boolean useIdentityColumn,
      EventSource source,
      boolean shouldDelayIdentityInserts) {
    if (useIdentityColumn) {
      EntityIdentityInsertAction insert =
          new EntityIdentityInsertAction(
              values,
              entity,
              persister,
              isVersionIncrementDisabled(),
              source,
              shouldDelayIdentityInserts);
      source.getActionQueue().addAction(insert);
      return insert;
    } else {
      Object version = Versioning.getVersion(values, persister);
      EntityInsertAction insert =
          new EntityInsertAction(
              id, values, entity, version, persister, isVersionIncrementDisabled(), source);
      source.getActionQueue().addAction(insert);
      return insert;
    }
  }

  protected Map getMergeMap(Object anything) {
    return null;
  }

  /**
   * After the save, will te version number be incremented if the instance is modified?
   *
   * @return True if the version will be incremented on an entity change after save; false
   *     otherwise.
   */
  protected boolean isVersionIncrementDisabled() {
    return false;
  }

  protected boolean visitCollectionsBeforeSave(
      Object entity, Serializable id, Object[] values, Type[] types, EventSource source) {
    WrapVisitor visitor = new WrapVisitor(source);
    // substitutes into values by side-effect
    visitor.processEntityPropertyValues(values, types);
    return visitor.isSubstitutionRequired();
  }

  /**
   * Perform any property value substitution that is necessary (interceptor callback, version
   * initialization...)
   *
   * @param entity The entity
   * @param id The entity identifier
   * @param values The snapshot entity state
   * @param persister The entity persister
   * @param source The originating session
   * @return True if the snapshot state changed such that reinjection of the values into the entity
   *     is required.
   */
  protected boolean substituteValuesIfNecessary(
      Object entity,
      Serializable id,
      Object[] values,
      EntityPersister persister,
      SessionImplementor source) {
    boolean substitute =
        source
            .getInterceptor()
            .onSave(entity, id, values, persister.getPropertyNames(), persister.getPropertyTypes());

    // keep the existing version number in the case of replicate!
    if (persister.isVersioned()) {
      substitute =
          Versioning.seedVersion(
                  values, persister.getVersionProperty(), persister.getVersionType(), source)
              || substitute;
    }
    return substitute;
  }

  /**
   * Handles the calls needed to perform pre-save cascades for the given entity.
   *
   * @param source The session from whcih the save event originated.
   * @param persister The entity's persister instance.
   * @param entity The entity to be saved.
   * @param anything Generally cascade-specific data
   */
  protected void cascadeBeforeSave(
      EventSource source, EntityPersister persister, Object entity, Object anything) {

    // cascade-save to many-to-one BEFORE the parent is saved
    source.getPersistenceContext().incrementCascadeLevel();
    try {
      Cascade.cascade(
          getCascadeAction(),
          CascadePoint.BEFORE_INSERT_AFTER_DELETE,
          source,
          persister,
          entity,
          anything);
    } finally {
      source.getPersistenceContext().decrementCascadeLevel();
    }
  }

  /**
   * Handles to calls needed to perform post-save cascades.
   *
   * @param source The session from which the event originated.
   * @param persister The entity's persister instance.
   * @param entity The entity beng saved.
   * @param anything Generally cascade-specific data
   */
  protected void cascadeAfterSave(
      EventSource source, EntityPersister persister, Object entity, Object anything) {

    // cascade-save to collections AFTER the collection owner was saved
    source.getPersistenceContext().incrementCascadeLevel();
    try {
      Cascade.cascade(
          getCascadeAction(),
          CascadePoint.AFTER_INSERT_BEFORE_DELETE,
          source,
          persister,
          entity,
          anything);
    } finally {
      source.getPersistenceContext().decrementCascadeLevel();
    }
  }

  protected abstract CascadingAction getCascadeAction();

  /**
   * Determine whether the entity is persistent, detached, or transient
   *
   * @param entity The entity to check
   * @param entityName The name of the entity
   * @param entry The entity's entry in the persistence context
   * @param source The originating session.
   * @return The state.
   */
  protected EntityState getEntityState(
      Object entity,
      String entityName,
      EntityEntry entry, // pass this as an argument only to avoid double looking
      SessionImplementor source) {

    final boolean traceEnabled = LOG.isTraceEnabled();
    if (entry != null) { // the object is persistent

      // the entity is associated with the session, so check its status
      if (entry.getStatus() != Status.DELETED) {
        // do nothing for persistent instances
        if (traceEnabled) {
          LOG.tracev("Persistent instance of: {0}", getLoggableName(entityName, entity));
        }
        return EntityState.PERSISTENT;
      }
      // ie. e.status==DELETED
      if (traceEnabled) {
        LOG.tracev("Deleted instance of: {0}", getLoggableName(entityName, entity));
      }
      return EntityState.DELETED;
    }
    // the object is transient or detached

    // the entity is not associated with the session, so
    // try interceptor and unsaved-value

    if (ForeignKeys.isTransient(entityName, entity, getAssumedUnsaved(), source)) {
      if (traceEnabled) {
        LOG.tracev("Transient instance of: {0}", getLoggableName(entityName, entity));
      }
      return EntityState.TRANSIENT;
    }
    if (traceEnabled) {
      LOG.tracev("Detached instance of: {0}", getLoggableName(entityName, entity));
    }
    return EntityState.DETACHED;
  }

  protected String getLoggableName(String entityName, Object entity) {
    return entityName == null ? entity.getClass().getName() : entityName;
  }

  protected Boolean getAssumedUnsaved() {
    return null;
  }
}
/** @author Steve Ebersole */
public class MetadataBuilderImpl implements MetadataBuilder, TypeContributions {
  private static final CoreMessageLogger log = CoreLogging.messageLogger(MetadataBuilderImpl.class);

  private final MetadataSources sources;
  private final MetadataBuildingOptionsImpl options;

  public MetadataBuilderImpl(MetadataSources sources) {
    this(sources, getStandardServiceRegistry(sources.getServiceRegistry()));
  }

  private static StandardServiceRegistry getStandardServiceRegistry(
      ServiceRegistry serviceRegistry) {
    if (serviceRegistry == null) {
      throw new HibernateException("ServiceRegistry passed to MetadataBuilder cannot be null");
    }

    if (StandardServiceRegistry.class.isInstance(serviceRegistry)) {
      return (StandardServiceRegistry) serviceRegistry;
    } else if (BootstrapServiceRegistry.class.isInstance(serviceRegistry)) {
      log.debugf(
          "ServiceRegistry passed to MetadataBuilder was a BootstrapServiceRegistry; this likely wont end well"
              + "if attempt is made to build SessionFactory");
      return new StandardServiceRegistryBuilder((BootstrapServiceRegistry) serviceRegistry).build();
    } else {
      throw new HibernateException(
          String.format(
              "Unexpected type of ServiceRegistry [%s] encountered in attempt to build MetadataBuilder",
              serviceRegistry.getClass().getName()));
    }
  }

  public MetadataBuilderImpl(MetadataSources sources, StandardServiceRegistry serviceRegistry) {
    this.sources = sources;
    this.options = new MetadataBuildingOptionsImpl(serviceRegistry);

    for (MetadataSourcesContributor contributor :
        sources
            .getServiceRegistry()
            .getService(ClassLoaderService.class)
            .loadJavaServices(MetadataSourcesContributor.class)) {
      contributor.contribute(sources);
    }

    // todo : not so sure this is needed anymore.
    //		these should be set during the StandardServiceRegistryBuilder.configure call
    applyCfgXmlValues(serviceRegistry.getService(CfgXmlAccessService.class));

    final ClassLoaderService classLoaderService =
        serviceRegistry.getService(ClassLoaderService.class);
    for (MetadataBuilderInitializer contributor :
        classLoaderService.loadJavaServices(MetadataBuilderInitializer.class)) {
      contributor.contribute(this, serviceRegistry);
    }
  }

  private void applyCfgXmlValues(CfgXmlAccessService service) {
    final LoadedConfig aggregatedConfig = service.getAggregatedConfig();
    if (aggregatedConfig == null) {
      return;
    }

    for (CacheRegionDefinition cacheRegionDefinition :
        aggregatedConfig.getCacheRegionDefinitions()) {
      applyCacheRegionDefinition(cacheRegionDefinition);
    }
  }

  @Override
  public MetadataBuilder applyImplicitSchemaName(String implicitSchemaName) {
    options.mappingDefaults.implicitSchemaName = implicitSchemaName;
    return this;
  }

  @Override
  public MetadataBuilder applyImplicitCatalogName(String implicitCatalogName) {
    options.mappingDefaults.implicitCatalogName = implicitCatalogName;
    return this;
  }

  @Override
  public MetadataBuilder applyImplicitNamingStrategy(ImplicitNamingStrategy namingStrategy) {
    this.options.implicitNamingStrategy = namingStrategy;
    return this;
  }

  @Override
  public MetadataBuilder applyPhysicalNamingStrategy(PhysicalNamingStrategy namingStrategy) {
    this.options.physicalNamingStrategy = namingStrategy;
    return this;
  }

  @Override
  public MetadataBuilder applyReflectionManager(ReflectionManager reflectionManager) {
    this.options.reflectionManager = reflectionManager;
    this.options.reflectionManager.injectClassLoaderDelegate(
        this.options.getHcannClassLoaderDelegate());
    return this;
  }

  @Override
  public MetadataBuilder applySharedCacheMode(SharedCacheMode sharedCacheMode) {
    this.options.sharedCacheMode = sharedCacheMode;
    return this;
  }

  @Override
  public MetadataBuilder applyAccessType(AccessType implicitCacheAccessType) {
    this.options.mappingDefaults.implicitCacheAccessType = implicitCacheAccessType;
    return this;
  }

  @Override
  public MetadataBuilder applyIndexView(IndexView jandexView) {
    this.options.jandexView = jandexView;
    return this;
  }

  @Override
  public MetadataBuilder applyScanOptions(ScanOptions scanOptions) {
    this.options.scanOptions = scanOptions;
    return this;
  }

  @Override
  public MetadataBuilder applyScanEnvironment(ScanEnvironment scanEnvironment) {
    this.options.scanEnvironment = scanEnvironment;
    return this;
  }

  @Override
  public MetadataBuilder applyScanner(Scanner scanner) {
    this.options.scannerSetting = scanner;
    return this;
  }

  @Override
  public MetadataBuilder applyArchiveDescriptorFactory(ArchiveDescriptorFactory factory) {
    this.options.archiveDescriptorFactory = factory;
    return this;
  }

  @Override
  public MetadataBuilder enableExplicitDiscriminatorsForJoinedSubclassSupport(boolean supported) {
    options.explicitDiscriminatorsForJoinedInheritanceSupported = supported;
    return this;
  }

  @Override
  public MetadataBuilder enableImplicitDiscriminatorsForJoinedSubclassSupport(boolean supported) {
    options.implicitDiscriminatorsForJoinedInheritanceSupported = supported;
    return this;
  }

  @Override
  public MetadataBuilder enableImplicitForcingOfDiscriminatorsInSelect(boolean supported) {
    options.implicitlyForceDiscriminatorInSelect = supported;
    return this;
  }

  @Override
  public MetadataBuilder enableGlobalNationalizedCharacterDataSupport(boolean enabled) {
    options.useNationalizedCharacterData = enabled;
    return this;
  }

  @Override
  public MetadataBuilder applyBasicType(BasicType type) {
    options.basicTypeRegistrations.add(type);
    return this;
  }

  @Override
  public MetadataBuilder applyBasicType(UserType type, String[] keys) {
    options.basicTypeRegistrations.add(new CustomType(type, keys));
    return this;
  }

  @Override
  public MetadataBuilder applyBasicType(CompositeUserType type, String[] keys) {
    options.basicTypeRegistrations.add(new CompositeCustomType(type, keys));
    return this;
  }

  @Override
  public MetadataBuilder applyTypes(TypeContributor typeContributor) {
    typeContributor.contribute(this, options.serviceRegistry);
    return this;
  }

  @Override
  public void contributeType(BasicType type) {
    options.basicTypeRegistrations.add(type);
  }

  @Override
  public void contributeType(UserType type, String[] keys) {
    options.basicTypeRegistrations.add(new CustomType(type, keys));
  }

  @Override
  public void contributeType(CompositeUserType type, String[] keys) {
    options.basicTypeRegistrations.add(new CompositeCustomType(type, keys));
  }

  @Override
  public MetadataBuilder applyCacheRegionDefinition(CacheRegionDefinition cacheRegionDefinition) {
    if (options.cacheRegionDefinitions == null) {
      options.cacheRegionDefinitions = new ArrayList<CacheRegionDefinition>();
    }
    options.cacheRegionDefinitions.add(cacheRegionDefinition);
    return this;
  }

  @Override
  public MetadataBuilder applyTempClassLoader(ClassLoader tempClassLoader) {
    options.tempClassLoader = tempClassLoader;
    return this;
  }

  @Override
  public MetadataBuilder applySourceProcessOrdering(MetadataSourceType... sourceTypes) {
    options.sourceProcessOrdering.addAll(Arrays.asList(sourceTypes));
    return this;
  }

  public MetadataBuilder allowSpecjSyntax() {
    this.options.specjProprietarySyntaxEnabled = true;
    return this;
  }

  @Override
  public MetadataBuilder applySqlFunction(String functionName, SQLFunction function) {
    if (this.options.sqlFunctionMap == null) {
      this.options.sqlFunctionMap = new HashMap<String, SQLFunction>();
    }
    this.options.sqlFunctionMap.put(functionName, function);
    return this;
  }

  @Override
  public MetadataBuilder applyAuxiliaryDatabaseObject(
      AuxiliaryDatabaseObject auxiliaryDatabaseObject) {
    if (this.options.auxiliaryDatabaseObjectList == null) {
      this.options.auxiliaryDatabaseObjectList = new ArrayList<AuxiliaryDatabaseObject>();
    }
    this.options.auxiliaryDatabaseObjectList.add(auxiliaryDatabaseObject);
    return this;
  }

  @Override
  public MetadataBuilder applyAttributeConverter(AttributeConverterDefinition definition) {
    this.options.addAttributeConverterDefinition(definition);
    return this;
  }

  @Override
  public MetadataBuilder applyAttributeConverter(
      Class<? extends AttributeConverter> attributeConverterClass) {
    applyAttributeConverter(AttributeConverterDefinition.from(attributeConverterClass));
    return this;
  }

  @Override
  public MetadataBuilder applyAttributeConverter(
      Class<? extends AttributeConverter> attributeConverterClass, boolean autoApply) {
    applyAttributeConverter(AttributeConverterDefinition.from(attributeConverterClass, autoApply));
    return this;
  }

  @Override
  public MetadataBuilder applyAttributeConverter(AttributeConverter attributeConverter) {
    applyAttributeConverter(AttributeConverterDefinition.from(attributeConverter));
    return this;
  }

  @Override
  public MetadataBuilder applyAttributeConverter(
      AttributeConverter attributeConverter, boolean autoApply) {
    applyAttributeConverter(AttributeConverterDefinition.from(attributeConverter, autoApply));
    return this;
  }

  @Override
  public MetadataBuilder enableNewIdentifierGeneratorSupport(boolean enabled) {
    if (enabled) {
      this.options.idGenerationTypeInterpreter.disableLegacyFallback();
    } else {
      this.options.idGenerationTypeInterpreter.enableLegacyFallback();
    }
    return this;
  }

  @Override
  public MetadataBuilder applyIdGenerationTypeInterpreter(
      IdGeneratorStrategyInterpreter interpreter) {
    this.options.idGenerationTypeInterpreter.addInterpreterDelegate(interpreter);
    return this;
  }

  //	public MetadataBuilder with(PersistentAttributeMemberResolver resolver) {
  //		options.persistentAttributeMemberResolver = resolver;
  //		return this;
  //	}

  @Override
  public MetadataImpl build() {
    final CfgXmlAccessService cfgXmlAccessService =
        options.serviceRegistry.getService(CfgXmlAccessService.class);
    if (cfgXmlAccessService.getAggregatedConfig() != null) {
      if (cfgXmlAccessService.getAggregatedConfig().getMappingReferences() != null) {
        for (MappingReference mappingReference :
            cfgXmlAccessService.getAggregatedConfig().getMappingReferences()) {
          mappingReference.apply(sources);
        }
      }
    }

    return MetadataBuildingProcess.build(sources, options);
  }

  public static class MappingDefaultsImpl implements MappingDefaults {
    private String implicitSchemaName;
    private String implicitCatalogName;
    private boolean implicitlyQuoteIdentifiers;

    private AccessType implicitCacheAccessType;

    public MappingDefaultsImpl(StandardServiceRegistry serviceRegistry) {
      final ConfigurationService configService =
          serviceRegistry.getService(ConfigurationService.class);

      this.implicitSchemaName =
          configService.getSetting(
              AvailableSettings.DEFAULT_SCHEMA, StandardConverters.STRING, null);

      this.implicitCatalogName =
          configService.getSetting(
              AvailableSettings.DEFAULT_CATALOG, StandardConverters.STRING, null);

      this.implicitlyQuoteIdentifiers =
          configService.getSetting(
              AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, StandardConverters.BOOLEAN, false);

      this.implicitCacheAccessType =
          configService.getSetting(
              AvailableSettings.DEFAULT_CACHE_CONCURRENCY_STRATEGY,
              new ConfigurationService.Converter<AccessType>() {
                @Override
                public AccessType convert(Object value) {
                  return AccessType.fromExternalName(value.toString());
                }
              });
    }

    @Override
    public String getImplicitSchemaName() {
      return implicitSchemaName;
    }

    @Override
    public String getImplicitCatalogName() {
      return implicitCatalogName;
    }

    @Override
    public boolean shouldImplicitlyQuoteIdentifiers() {
      return implicitlyQuoteIdentifiers;
    }

    @Override
    public String getImplicitIdColumnName() {
      return DEFAULT_IDENTIFIER_COLUMN_NAME;
    }

    @Override
    public String getImplicitTenantIdColumnName() {
      return DEFAULT_TENANT_IDENTIFIER_COLUMN_NAME;
    }

    @Override
    public String getImplicitDiscriminatorColumnName() {
      return DEFAULT_DISCRIMINATOR_COLUMN_NAME;
    }

    @Override
    public String getImplicitPackageName() {
      return null;
    }

    @Override
    public boolean isAutoImportEnabled() {
      return true;
    }

    @Override
    public String getImplicitCascadeStyleName() {
      return DEFAULT_CASCADE_NAME;
    }

    @Override
    public String getImplicitPropertyAccessorName() {
      return DEFAULT_PROPERTY_ACCESS_NAME;
    }

    @Override
    public boolean areEntitiesImplicitlyLazy() {
      // for now, just hard-code
      return false;
    }

    @Override
    public boolean areCollectionsImplicitlyLazy() {
      // for now, just hard-code
      return true;
    }

    @Override
    public AccessType getImplicitCacheAccessType() {
      return implicitCacheAccessType;
    }
  }

  public static class MetadataBuildingOptionsImpl implements MetadataBuildingOptions {
    private final StandardServiceRegistry serviceRegistry;
    private final MappingDefaultsImpl mappingDefaults;

    private ArrayList<BasicType> basicTypeRegistrations = new ArrayList<BasicType>();

    private IndexView jandexView;
    private ClassLoader tempClassLoader;

    private ScanOptions scanOptions;
    private ScanEnvironment scanEnvironment;
    private Object scannerSetting;
    private ArchiveDescriptorFactory archiveDescriptorFactory;

    private ImplicitNamingStrategy implicitNamingStrategy;
    private PhysicalNamingStrategy physicalNamingStrategy;

    private ReflectionManager reflectionManager;
    private ClassLoaderDelegate hcannClassLoaderDelegate;

    private SharedCacheMode sharedCacheMode;
    private AccessType defaultCacheAccessType;
    private MultiTenancyStrategy multiTenancyStrategy;
    private ArrayList<CacheRegionDefinition> cacheRegionDefinitions;
    private boolean explicitDiscriminatorsForJoinedInheritanceSupported;
    private boolean implicitDiscriminatorsForJoinedInheritanceSupported;
    private boolean implicitlyForceDiscriminatorInSelect;
    private boolean useNationalizedCharacterData;
    private boolean specjProprietarySyntaxEnabled;
    private ArrayList<MetadataSourceType> sourceProcessOrdering;

    private HashMap<String, SQLFunction> sqlFunctionMap;
    private ArrayList<AuxiliaryDatabaseObject> auxiliaryDatabaseObjectList;
    private HashMap<Class, AttributeConverterDefinition> attributeConverterDefinitionsByClass;

    private IdGeneratorInterpreterImpl idGenerationTypeInterpreter =
        new IdGeneratorInterpreterImpl();

    private boolean autoQuoteKeywords;

    //		private PersistentAttributeMemberResolver persistentAttributeMemberResolver =
    //				StandardPersistentAttributeMemberResolver.INSTANCE;

    public MetadataBuildingOptionsImpl(StandardServiceRegistry serviceRegistry) {
      this.serviceRegistry = serviceRegistry;

      final StrategySelector strategySelector = serviceRegistry.getService(StrategySelector.class);
      final ConfigurationService configService =
          serviceRegistry.getService(ConfigurationService.class);

      this.mappingDefaults = new MappingDefaultsImpl(serviceRegistry);

      //			jandexView = (IndexView) configService.getSettings().get( AvailableSettings.JANDEX_INDEX
      // );

      scanOptions =
          new StandardScanOptions(
              (String) configService.getSettings().get(AvailableSettings.SCANNER_DISCOVERY), false);
      // ScanEnvironment must be set explicitly
      scannerSetting = configService.getSettings().get(AvailableSettings.SCANNER);
      if (scannerSetting == null) {
        scannerSetting = configService.getSettings().get(AvailableSettings.SCANNER_DEPRECATED);
        if (scannerSetting != null) {
          DEPRECATION_LOGGER.logDeprecatedScannerSetting();
        }
      }
      archiveDescriptorFactory =
          strategySelector.resolveStrategy(
              ArchiveDescriptorFactory.class,
              configService.getSettings().get(AvailableSettings.SCANNER_ARCHIVE_INTERPRETER));

      multiTenancyStrategy =
          MultiTenancyStrategy.determineMultiTenancyStrategy(configService.getSettings());

      implicitDiscriminatorsForJoinedInheritanceSupported =
          configService.getSetting(
              AvailableSettings.IMPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS,
              StandardConverters.BOOLEAN,
              false);

      explicitDiscriminatorsForJoinedInheritanceSupported =
          !configService.getSetting(
              AvailableSettings.IGNORE_EXPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS,
              StandardConverters.BOOLEAN,
              false);

      implicitlyForceDiscriminatorInSelect =
          configService.getSetting(
              AvailableSettings.FORCE_DISCRIMINATOR_IN_SELECTS_BY_DEFAULT,
              StandardConverters.BOOLEAN,
              false);

      sharedCacheMode =
          configService.getSetting(
              "javax.persistence.sharedCache.mode",
              new ConfigurationService.Converter<SharedCacheMode>() {
                @Override
                public SharedCacheMode convert(Object value) {
                  if (value == null) {
                    return null;
                  }

                  if (SharedCacheMode.class.isInstance(value)) {
                    return (SharedCacheMode) value;
                  }

                  return SharedCacheMode.valueOf(value.toString());
                }
              },
              SharedCacheMode.UNSPECIFIED);

      defaultCacheAccessType =
          configService.getSetting(
              AvailableSettings.DEFAULT_CACHE_CONCURRENCY_STRATEGY,
              new ConfigurationService.Converter<AccessType>() {
                @Override
                public AccessType convert(Object value) {
                  if (value == null) {
                    return null;
                  }

                  if (CacheConcurrencyStrategy.class.isInstance(value)) {
                    return ((CacheConcurrencyStrategy) value).toAccessType();
                  }

                  if (AccessType.class.isInstance(value)) {
                    return (AccessType) value;
                  }

                  return AccessType.fromExternalName(value.toString());
                }
              },
              // by default, see if the defined RegionFactory (if one) defines a default
              serviceRegistry.getService(RegionFactory.class) == null
                  ? null
                  : serviceRegistry.getService(RegionFactory.class).getDefaultAccessType());

      specjProprietarySyntaxEnabled =
          configService.getSetting(
              "hibernate.enable_specj_proprietary_syntax", StandardConverters.BOOLEAN, false);

      implicitNamingStrategy =
          strategySelector.resolveDefaultableStrategy(
              ImplicitNamingStrategy.class,
              configService.getSettings().get(AvailableSettings.IMPLICIT_NAMING_STRATEGY),
              ImplicitNamingStrategyLegacyJpaImpl.INSTANCE);

      physicalNamingStrategy =
          strategySelector.resolveDefaultableStrategy(
              PhysicalNamingStrategy.class,
              configService.getSettings().get(AvailableSettings.PHYSICAL_NAMING_STRATEGY),
              PhysicalNamingStrategyStandardImpl.INSTANCE);

      sourceProcessOrdering = resolveInitialSourceProcessOrdering(configService);

      final boolean useNewIdentifierGenerators =
          configService.getSetting(
              AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, StandardConverters.BOOLEAN, false);
      if (useNewIdentifierGenerators) {
        idGenerationTypeInterpreter.disableLegacyFallback();
      } else {
        idGenerationTypeInterpreter.enableLegacyFallback();
      }

      reflectionManager = generateDefaultReflectionManager();
    }

    private ArrayList<MetadataSourceType> resolveInitialSourceProcessOrdering(
        ConfigurationService configService) {
      final ArrayList<MetadataSourceType> initialSelections = new ArrayList<MetadataSourceType>();

      final String sourceProcessOrderingSetting =
          configService.getSetting(
              AvailableSettings.ARTIFACT_PROCESSING_ORDER, StandardConverters.STRING);
      if (sourceProcessOrderingSetting != null) {
        final String[] orderChoices =
            StringHelper.split(",; ", sourceProcessOrderingSetting, false);
        initialSelections.addAll(
            CollectionHelper.<MetadataSourceType>arrayList(orderChoices.length));
        for (String orderChoice : orderChoices) {
          initialSelections.add(MetadataSourceType.parsePrecedence(orderChoice));
        }
      }
      if (initialSelections.isEmpty()) {
        initialSelections.add(MetadataSourceType.HBM);
        initialSelections.add(MetadataSourceType.CLASS);
      }

      return initialSelections;
    }

    private ReflectionManager generateDefaultReflectionManager() {
      final JavaReflectionManager reflectionManager = new JavaReflectionManager();
      reflectionManager.setMetadataProvider(new JPAMetadataProvider(this));
      reflectionManager.injectClassLoaderDelegate(getHcannClassLoaderDelegate());
      return reflectionManager;
    }

    public ClassLoaderDelegate getHcannClassLoaderDelegate() {
      if (hcannClassLoaderDelegate == null) {
        hcannClassLoaderDelegate =
            new ClassLoaderDelegate() {
              private final ClassLoaderService classLoaderService =
                  getServiceRegistry().getService(ClassLoaderService.class);

              @Override
              public <T> Class<T> classForName(String className) throws ClassLoadingException {
                try {
                  return classLoaderService.classForName(className);
                } catch (org.hibernate.boot.registry.classloading.spi.ClassLoadingException e) {
                  return StandardClassLoaderDelegateImpl.INSTANCE.classForName(className);
                }
              }
            };
      }
      return hcannClassLoaderDelegate;
    }

    @Override
    public StandardServiceRegistry getServiceRegistry() {
      return serviceRegistry;
    }

    @Override
    public MappingDefaults getMappingDefaults() {
      return mappingDefaults;
    }

    @Override
    public List<BasicType> getBasicTypeRegistrations() {
      return basicTypeRegistrations;
    }

    @Override
    public IndexView getJandexView() {
      return jandexView;
    }

    @Override
    public ScanOptions getScanOptions() {
      return scanOptions;
    }

    @Override
    public ScanEnvironment getScanEnvironment() {
      return scanEnvironment;
    }

    @Override
    public Object getScanner() {
      return scannerSetting;
    }

    @Override
    public ArchiveDescriptorFactory getArchiveDescriptorFactory() {
      return archiveDescriptorFactory;
    }

    @Override
    public ClassLoader getTempClassLoader() {
      return tempClassLoader;
    }

    @Override
    public ImplicitNamingStrategy getImplicitNamingStrategy() {
      return implicitNamingStrategy;
    }

    @Override
    public PhysicalNamingStrategy getPhysicalNamingStrategy() {
      return physicalNamingStrategy;
    }

    @Override
    public ReflectionManager getReflectionManager() {
      return reflectionManager;
    }

    @Override
    public SharedCacheMode getSharedCacheMode() {
      return sharedCacheMode;
    }

    @Override
    public AccessType getImplicitCacheAccessType() {
      return defaultCacheAccessType;
    }

    @Override
    public MultiTenancyStrategy getMultiTenancyStrategy() {
      return multiTenancyStrategy;
    }

    @Override
    public IdGeneratorStrategyInterpreter getIdGenerationTypeInterpreter() {
      return idGenerationTypeInterpreter;
    }

    @Override
    public List<CacheRegionDefinition> getCacheRegionDefinitions() {
      return cacheRegionDefinitions;
    }

    @Override
    public boolean ignoreExplicitDiscriminatorsForJoinedInheritance() {
      return !explicitDiscriminatorsForJoinedInheritanceSupported;
    }

    @Override
    public boolean createImplicitDiscriminatorsForJoinedInheritance() {
      return implicitDiscriminatorsForJoinedInheritanceSupported;
    }

    @Override
    public boolean shouldImplicitlyForceDiscriminatorInSelect() {
      return implicitlyForceDiscriminatorInSelect;
    }

    @Override
    public boolean useNationalizedCharacterData() {
      return useNationalizedCharacterData;
    }

    @Override
    public boolean isSpecjProprietarySyntaxEnabled() {
      return specjProprietarySyntaxEnabled;
    }

    @Override
    public List<MetadataSourceType> getSourceProcessOrdering() {
      return sourceProcessOrdering;
    }

    @Override
    public Map<String, SQLFunction> getSqlFunctions() {
      return sqlFunctionMap == null ? Collections.<String, SQLFunction>emptyMap() : sqlFunctionMap;
    }

    @Override
    public List<AuxiliaryDatabaseObject> getAuxiliaryDatabaseObjectList() {
      return auxiliaryDatabaseObjectList == null
          ? Collections.<AuxiliaryDatabaseObject>emptyList()
          : auxiliaryDatabaseObjectList;
    }

    @Override
    public List<AttributeConverterDefinition> getAttributeConverters() {
      return attributeConverterDefinitionsByClass == null
          ? Collections.<AttributeConverterDefinition>emptyList()
          : new ArrayList<AttributeConverterDefinition>(
              attributeConverterDefinitionsByClass.values());
    }

    public void addAttributeConverterDefinition(AttributeConverterDefinition definition) {
      if (this.attributeConverterDefinitionsByClass == null) {
        this.attributeConverterDefinitionsByClass =
            new HashMap<Class, AttributeConverterDefinition>();
      }

      final Object old =
          this.attributeConverterDefinitionsByClass.put(
              definition.getAttributeConverter().getClass(), definition);

      if (old != null) {
        throw new AssertionFailure(
            String.format(
                "AttributeConverter class [%s] registered multiple times",
                definition.getAttributeConverter().getClass()));
      }
    }

    public static interface JpaOrmXmlPersistenceUnitDefaults {
      public String getDefaultSchemaName();

      public String getDefaultCatalogName();

      public boolean shouldImplicitlyQuoteIdentifiers();
    }

    /**
     * Yuck. This is needed because JPA lets users define "global building options" in {@code
     * orm.xml} mappings. Forget that there are generally multiple {@code orm.xml} mappings if using
     * XML approach... Ugh
     */
    public void apply(JpaOrmXmlPersistenceUnitDefaults jpaOrmXmlPersistenceUnitDefaults) {
      if (!mappingDefaults.shouldImplicitlyQuoteIdentifiers()) {
        mappingDefaults.implicitlyQuoteIdentifiers =
            jpaOrmXmlPersistenceUnitDefaults.shouldImplicitlyQuoteIdentifiers();
      }

      if (mappingDefaults.getImplicitCatalogName() == null) {
        mappingDefaults.implicitCatalogName =
            StringHelper.nullIfEmpty(jpaOrmXmlPersistenceUnitDefaults.getDefaultCatalogName());
      }

      if (mappingDefaults.getImplicitSchemaName() == null) {
        mappingDefaults.implicitSchemaName =
            StringHelper.nullIfEmpty(jpaOrmXmlPersistenceUnitDefaults.getDefaultSchemaName());
      }
    }

    //		@Override
    //		public PersistentAttributeMemberResolver getPersistentAttributeMemberResolver() {
    //			return persistentAttributeMemberResolver;
    //		}
  }
}
/**
 * A registry of {@link BasicType} instances
 *
 * @author Steve Ebersole
 */
public class BasicTypeRegistry implements Serializable {
  private static final CoreMessageLogger LOG = CoreLogging.messageLogger(BasicTypeRegistry.class);

  // TODO : analyze these sizing params; unfortunately this seems to be the only way to give a
  // "concurrencyLevel"
  private Map<String, BasicType> registry = new ConcurrentHashMap<String, BasicType>(100, .75f, 1);
  private boolean locked;

  public BasicTypeRegistry() {
    register(BooleanType.INSTANCE);
    register(NumericBooleanType.INSTANCE);
    register(TrueFalseType.INSTANCE);
    register(YesNoType.INSTANCE);

    register(ByteType.INSTANCE);
    register(CharacterType.INSTANCE);
    register(ShortType.INSTANCE);
    register(IntegerType.INSTANCE);
    register(LongType.INSTANCE);
    register(FloatType.INSTANCE);
    register(DoubleType.INSTANCE);
    register(BigDecimalType.INSTANCE);
    register(BigIntegerType.INSTANCE);

    register(StringType.INSTANCE);
    register(StringNVarcharType.INSTANCE);
    register(CharacterNCharType.INSTANCE);
    register(UrlType.INSTANCE);

    register(DateType.INSTANCE);
    register(TimeType.INSTANCE);
    register(TimestampType.INSTANCE);
    register(DbTimestampType.INSTANCE);
    register(CalendarType.INSTANCE);
    register(CalendarDateType.INSTANCE);

    register(LocaleType.INSTANCE);
    register(CurrencyType.INSTANCE);
    register(TimeZoneType.INSTANCE);
    register(ClassType.INSTANCE);
    register(UUIDBinaryType.INSTANCE);
    register(UUIDCharType.INSTANCE);

    register(BinaryType.INSTANCE);
    register(WrapperBinaryType.INSTANCE);
    register(ImageType.INSTANCE);
    register(CharArrayType.INSTANCE);
    register(CharacterArrayType.INSTANCE);
    register(TextType.INSTANCE);
    register(NTextType.INSTANCE);
    register(BlobType.INSTANCE);
    register(MaterializedBlobType.INSTANCE);
    register(ClobType.INSTANCE);
    register(NClobType.INSTANCE);
    register(MaterializedClobType.INSTANCE);
    register(MaterializedNClobType.INSTANCE);
    register(SerializableType.INSTANCE);

    register(ObjectType.INSTANCE);

    //noinspection unchecked
    register(new AdaptedImmutableType(DateType.INSTANCE));
    //noinspection unchecked
    register(new AdaptedImmutableType(TimeType.INSTANCE));
    //noinspection unchecked
    register(new AdaptedImmutableType(TimestampType.INSTANCE));
    //noinspection unchecked
    register(new AdaptedImmutableType(DbTimestampType.INSTANCE));
    //noinspection unchecked
    register(new AdaptedImmutableType(CalendarType.INSTANCE));
    //noinspection unchecked
    register(new AdaptedImmutableType(CalendarDateType.INSTANCE));
    //noinspection unchecked
    register(new AdaptedImmutableType(BinaryType.INSTANCE));
    //noinspection unchecked
    register(new AdaptedImmutableType(SerializableType.INSTANCE));
  }

  /**
   * Constructor version used during shallow copy
   *
   * @param registeredTypes The type map to copy over
   */
  @SuppressWarnings({"UnusedDeclaration"})
  private BasicTypeRegistry(Map<String, BasicType> registeredTypes) {
    registry.putAll(registeredTypes);
    locked = true;
  }

  public void register(BasicType type) {
    if (locked) {
      throw new HibernateException("Can not alter TypeRegistry at this time");
    }

    if (type == null) {
      throw new HibernateException("Type to register cannot be null");
    }

    if (type.getRegistrationKeys() == null || type.getRegistrationKeys().length == 0) {
      LOG.typeDefinedNoRegistrationKeys(type);
    }

    for (String key : type.getRegistrationKeys()) {
      // be safe...
      if (key == null) continue;
      LOG.debugf("Adding type registration %s -> %s", key, type);
      final Type old = registry.put(key, type);
      if (old != null && old != type) LOG.typeRegistrationOverridesPrevious(key, old);
    }
  }

  public void register(UserType type, String[] keys) {
    register(new CustomType(type, keys));
  }

  public void register(CompositeUserType type, String[] keys) {
    register(new CompositeCustomType(type, keys));
  }

  public BasicType getRegisteredType(String key) {
    return registry.get(key);
  }

  public BasicTypeRegistry shallowCopy() {
    return new BasicTypeRegistry(this.registry);
  }
}
/**
 * An {@link EntityTuplizer} specific to the dynamic-map entity mode.
 *
 * @author Steve Ebersole
 * @author Gavin King
 */
public class DynamicMapEntityTuplizer extends AbstractEntityTuplizer {
  private static final CoreMessageLogger LOG =
      CoreLogging.messageLogger(DynamicMapEntityTuplizer.class);

  DynamicMapEntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity) {
    super(entityMetamodel, mappedEntity);
  }

  @Override
  public EntityMode getEntityMode() {
    return EntityMode.MAP;
  }

  private PropertyAccessor buildPropertyAccessor(Property mappedProperty) {
    if (mappedProperty.isBackRef()) {
      return mappedProperty.getPropertyAccessor(null);
    } else {
      return PropertyAccessorFactory.getDynamicMapPropertyAccessor();
    }
  }

  @Override
  protected Getter buildPropertyGetter(Property mappedProperty, PersistentClass mappedEntity) {
    return buildPropertyAccessor(mappedProperty).getGetter(null, mappedProperty.getName());
  }

  @Override
  protected Setter buildPropertySetter(Property mappedProperty, PersistentClass mappedEntity) {
    return buildPropertyAccessor(mappedProperty).getSetter(null, mappedProperty.getName());
  }

  @Override
  protected Instantiator buildInstantiator(PersistentClass mappingInfo) {
    return new DynamicMapInstantiator(mappingInfo);
  }

  @Override
  protected ProxyFactory buildProxyFactory(
      PersistentClass mappingInfo, Getter idGetter, Setter idSetter) {

    ProxyFactory pf = new MapProxyFactory();
    try {
      // TODO: design new lifecycle for ProxyFactory
      pf.postInstantiate(getEntityName(), null, null, null, null, null);
    } catch (HibernateException he) {
      LOG.unableToCreateProxyFactory(getEntityName(), he);
      pf = null;
    }
    return pf;
  }

  @Override
  public Class getMappedClass() {
    return Map.class;
  }

  @Override
  public Class getConcreteProxyClass() {
    return Map.class;
  }

  @Override
  public boolean isInstrumented() {
    return false;
  }

  @Override
  public EntityNameResolver[] getEntityNameResolvers() {
    return new EntityNameResolver[] {BasicEntityNameResolver.INSTANCE};
  }

  @Override
  public String determineConcreteSubclassEntityName(
      Object entityInstance, SessionFactoryImplementor factory) {
    return extractEmbeddedEntityName((Map) entityInstance);
  }

  public static String extractEmbeddedEntityName(Map entity) {
    return (String) entity.get(DynamicMapInstantiator.KEY);
  }

  public static class BasicEntityNameResolver implements EntityNameResolver {
    public static final BasicEntityNameResolver INSTANCE = new BasicEntityNameResolver();

    @Override
    public String resolveEntityName(Object entity) {
      if (!Map.class.isInstance(entity)) {
        return null;
      }
      final String entityName = extractEmbeddedEntityName((Map) entity);
      if (entityName == null) {
        throw new HibernateException("Could not determine type of dynamic map entity");
      }
      return entityName;
    }

    @Override
    public boolean equals(Object obj) {
      return getClass().equals(obj.getClass());
    }

    @Override
    public int hashCode() {
      return getClass().hashCode();
    }
  }
}
/**
 * enhancer for persistent attributes of any type of entity
 *
 * @author <a href="mailto:[email protected]">Luis Barreiro</a>
 */
public class PersistentAttributesEnhancer extends Enhancer {

  private static final CoreMessageLogger log =
      CoreLogging.messageLogger(PersistentAttributesEnhancer.class);

  public PersistentAttributesEnhancer(EnhancementContext context) {
    super(context);
  }

  public void enhance(CtClass managedCtClass) {
    final IdentityHashMap<String, PersistentAttributeAccessMethods> attrDescriptorMap =
        new IdentityHashMap<String, PersistentAttributeAccessMethods>();

    for (CtField persistentField : collectPersistentFields(managedCtClass)) {
      attrDescriptorMap.put(
          persistentField.getName(), enhancePersistentAttribute(managedCtClass, persistentField));
    }

    // find all references to the transformed fields and replace with calls to the added
    // reader/writer methods
    enhanceAttributesAccess(managedCtClass, attrDescriptorMap);

    // same thing for direct access to fields of other entities
    if (this.enhancementContext.doFieldAccessEnhancement(managedCtClass)) {
      enhanceFieldAccess(managedCtClass);
    }
  }

  private CtField[] collectPersistentFields(CtClass managedCtClass) {
    final List<CtField> persistentFieldList = new LinkedList<CtField>();
    for (CtField ctField : managedCtClass.getDeclaredFields()) {
      // skip static fields and skip fields added by enhancement
      if (Modifier.isStatic(ctField.getModifiers())
          || ctField.getName().startsWith("$$_hibernate_")) {
        continue;
      }
      // skip outer reference in inner classes
      if ("this$0".equals(ctField.getName())) {
        continue;
      }
      if (enhancementContext.isPersistentField(ctField)) {
        persistentFieldList.add(ctField);
      }
    }
    return enhancementContext.order(
        persistentFieldList.toArray(new CtField[persistentFieldList.size()]));
  }

  private PersistentAttributeAccessMethods enhancePersistentAttribute(
      CtClass managedCtClass, CtField persistentField) {
    try {
      final AttributeTypeDescriptor typeDescriptor =
          AttributeTypeDescriptor.resolve(persistentField);
      return new PersistentAttributeAccessMethods(
          generateFieldReader(managedCtClass, persistentField, typeDescriptor),
          generateFieldWriter(managedCtClass, persistentField, typeDescriptor));
    } catch (Exception e) {
      final String msg =
          String.format(
              "Unable to enhance persistent attribute [%s:%s]",
              managedCtClass.getName(), persistentField.getName());
      throw new EnhancementException(msg, e);
    }
  }

  private CtMethod generateFieldReader(
      CtClass managedCtClass, CtField persistentField, AttributeTypeDescriptor typeDescriptor) {
    final String fieldName = persistentField.getName();
    final String readerName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + fieldName;

    // read attempts only have to deal lazy-loading support, not dirty checking;
    // so if the field is not enabled as lazy-loadable return a plain simple getter as the reader
    if (!enhancementContext.isLazyLoadable(persistentField)) {
      return MethodWriter.addGetter(managedCtClass, fieldName, readerName);
    }

    try {
      return MethodWriter.write(
          managedCtClass,
          "public %s %s() {%n  %s%n  return this.%s;%n}",
          persistentField.getType().getName(),
          readerName,
          typeDescriptor.buildReadInterceptionBodyFragment(fieldName),
          fieldName);
    } catch (CannotCompileException cce) {
      final String msg =
          String.format(
              "Could not enhance entity class [%s] to add field reader method [%s]",
              managedCtClass.getName(), readerName);
      throw new EnhancementException(msg, cce);
    } catch (NotFoundException nfe) {
      final String msg =
          String.format(
              "Could not enhance entity class [%s] to add field reader method [%s]",
              managedCtClass.getName(), readerName);
      throw new EnhancementException(msg, nfe);
    }
  }

  private CtMethod generateFieldWriter(
      CtClass managedCtClass, CtField persistentField, AttributeTypeDescriptor typeDescriptor) {
    final String fieldName = persistentField.getName();
    final String writerName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName;

    try {
      final CtMethod writer;

      if (!enhancementContext.isLazyLoadable(persistentField)) {
        writer = MethodWriter.addSetter(managedCtClass, fieldName, writerName);
      } else {
        writer =
            MethodWriter.write(
                managedCtClass,
                "public void %s(%s %s) {%n  %s%n}",
                writerName,
                persistentField.getType().getName(),
                fieldName,
                typeDescriptor.buildWriteInterceptionBodyFragment(fieldName));
      }

      if (enhancementContext.isCompositeClass(managedCtClass)) {
        writer.insertBefore(
            String.format(
                "if (%s != null) { %<s.callOwner(\".%s\"); }%n",
                EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME, fieldName));
      } else if (enhancementContext.doDirtyCheckingInline(managedCtClass)) {
        writer.insertBefore(
            typeDescriptor.buildInLineDirtyCheckingBodyFragment(
                enhancementContext, persistentField));
      }

      handleCompositeField(managedCtClass, persistentField, writer);

      if (enhancementContext.doBiDirectionalAssociationManagement(persistentField)) {
        handleBiDirectionalAssociation(managedCtClass, persistentField, writer);
      }
      return writer;
    } catch (CannotCompileException cce) {
      final String msg =
          String.format(
              "Could not enhance entity class [%s] to add field writer method [%s]",
              managedCtClass.getName(), writerName);
      throw new EnhancementException(msg, cce);
    } catch (NotFoundException nfe) {
      final String msg =
          String.format(
              "Could not enhance entity class [%s] to add field writer method [%s]",
              managedCtClass.getName(), writerName);
      throw new EnhancementException(msg, nfe);
    }
  }

  private void handleBiDirectionalAssociation(
      CtClass managedCtClass, CtField persistentField, CtMethod fieldWriter)
      throws NotFoundException, CannotCompileException {
    if (!isPossibleBiDirectionalAssociation(persistentField)) {
      return;
    }
    final CtClass targetEntity = getTargetEntityClass(persistentField);
    if (targetEntity == null) {
      log.debugf(
          "Could not find type of bi-directional association for field [%s#%s]",
          managedCtClass.getName(), persistentField.getName());
      return;
    }
    final String mappedBy = getMappedBy(persistentField, targetEntity);
    if (mappedBy.isEmpty()) {
      log.warnf(
          "Could not find bi-directional association for field [%s#%s]",
          managedCtClass.getName(), persistentField.getName());
      return;
    }

    // create a temporary getter and setter on the target entity to be able to compile our code
    final String mappedByGetterName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + mappedBy;
    final String mappedBySetterName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + mappedBy;
    MethodWriter.addGetter(targetEntity, mappedBy, mappedByGetterName);
    MethodWriter.addSetter(targetEntity, mappedBy, mappedBySetterName);

    if (persistentField.hasAnnotation(OneToOne.class)) {
      // only unset when $1 != null to avoid recursion
      fieldWriter.insertBefore(
          String.format(
              "if ($0.%s != null && $1 != null) $0.%<s.%s(null);%n",
              persistentField.getName(), mappedBySetterName));
      fieldWriter.insertAfter(
          String.format(
              "if ($1 != null && $1.%s() != $0) $1.%s($0);%n",
              mappedByGetterName, mappedBySetterName));
    }
    if (persistentField.hasAnnotation(OneToMany.class)) {
      // only remove elements not in the new collection or else we would loose those elements
      // don't use iterator to avoid ConcurrentModException
      fieldWriter.insertBefore(
          String.format(
              "if ($0.%s != null) { Object[] array = $0.%<s.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; if ($1 == null || !$1.contains(target)) target.%s(null); } }%n",
              persistentField.getName(), targetEntity.getName(), mappedBySetterName));
      fieldWriter.insertAfter(
          String.format(
              "if ($1 != null) { Object[] array = $1.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; if (target.%s() != $0) target.%s((%s)$0); } }%n",
              targetEntity.getName(),
              mappedByGetterName,
              mappedBySetterName,
              managedCtClass.getName()));
    }
    if (persistentField.hasAnnotation(ManyToOne.class)) {
      fieldWriter.insertBefore(
          String.format(
              "if ($0.%1$s != null && $0.%1$s.%2$s() != null) $0.%1$s.%2$s().remove($0);%n",
              persistentField.getName(), mappedByGetterName));
      // check .contains($0) to avoid double inserts (but preventing duplicates)
      fieldWriter.insertAfter(
          String.format(
              "if ($1 != null) { java.util.Collection c = $1.%s(); if (c != null && !c.contains($0)) c.add($0); }%n",
              mappedByGetterName));
    }
    if (persistentField.hasAnnotation(ManyToMany.class)) {
      fieldWriter.insertBefore(
          String.format(
              "if ($0.%s != null) { Object[] array = $0.%<s.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; if ($1 == null || !$1.contains(target)) target.%s().remove($0); } }%n",
              persistentField.getName(), targetEntity.getName(), mappedByGetterName));
      fieldWriter.insertAfter(
          String.format(
              "if ($1 != null) { Object[] array = $1.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; java.util.Collection c = target.%s(); if ( c != $0 && c != null) c.add($0); } }%n",
              targetEntity.getName(), mappedByGetterName));
    }
    // implementation note: association management @OneToMany and @ManyToMay works for add()
    // operations but for remove() a snapshot of the collection is needed so we know what
    // associations to break.
    // another approach that could force that behavior would be to return
    // Collections.unmodifiableCollection() ...
  }

  private boolean isPossibleBiDirectionalAssociation(CtField persistentField) {
    return persistentField.hasAnnotation(OneToOne.class)
        || persistentField.hasAnnotation(OneToMany.class)
        || persistentField.hasAnnotation(ManyToOne.class)
        || persistentField.hasAnnotation(ManyToMany.class);
  }

  private String getMappedBy(CtField persistentField, CtClass targetEntity) {
    final String local = getMappedByFromAnnotation(persistentField);
    return local.isEmpty() ? getMappedByFromTargetEntity(persistentField, targetEntity) : local;
  }

  private String getMappedByFromAnnotation(CtField persistentField) {
    try {
      if (persistentField.hasAnnotation(OneToOne.class)) {
        return ((OneToOne) persistentField.getAnnotation(OneToOne.class)).mappedBy();
      }
      if (persistentField.hasAnnotation(OneToMany.class)) {
        return ((OneToMany) persistentField.getAnnotation(OneToMany.class)).mappedBy();
      }
      // For @ManyToOne associations, mappedBy must come from the @OneToMany side of the association
      if (persistentField.hasAnnotation(ManyToMany.class)) {
        return ((ManyToMany) persistentField.getAnnotation(ManyToMany.class)).mappedBy();
      }
    } catch (ClassNotFoundException ignore) {
    }
    return "";
  }

  private String getMappedByFromTargetEntity(CtField persistentField, CtClass targetEntity) {
    // get mappedBy value by searching in the fields of the target entity class
    for (CtField f : targetEntity.getDeclaredFields()) {
      if (enhancementContext.isPersistentField(f)
          && getMappedByFromAnnotation(f).equals(persistentField.getName())) {
        log.debugf(
            "mappedBy association for field [%s:%s] is [%s:%s]",
            persistentField.getDeclaringClass().getName(),
            persistentField.getName(),
            targetEntity.getName(),
            f.getName());
        return f.getName();
      }
    }
    return "";
  }

  private CtClass getTargetEntityClass(CtField persistentField) throws NotFoundException {
    // get targetEntity defined in the annotation
    try {
      Class<?> targetClass = null;
      if (persistentField.hasAnnotation(OneToOne.class)) {
        targetClass = ((OneToOne) persistentField.getAnnotation(OneToOne.class)).targetEntity();
      }
      if (persistentField.hasAnnotation(OneToMany.class)) {
        targetClass = ((OneToMany) persistentField.getAnnotation(OneToMany.class)).targetEntity();
      }
      if (persistentField.hasAnnotation(ManyToOne.class)) {
        targetClass = ((ManyToOne) persistentField.getAnnotation(ManyToOne.class)).targetEntity();
      }
      if (persistentField.hasAnnotation(ManyToMany.class)) {
        targetClass = ((ManyToMany) persistentField.getAnnotation(ManyToMany.class)).targetEntity();
      }
      if (targetClass != null && targetClass != void.class) {
        return classPool.get(targetClass.getName());
      }
    } catch (ClassNotFoundException ignore) {
    }

    // infer targetEntity from generic type signature
    if (persistentField.hasAnnotation(OneToOne.class)
        || persistentField.hasAnnotation(ManyToOne.class)) {
      return persistentField.getType();
    }
    if (persistentField.hasAnnotation(OneToMany.class)
        || persistentField.hasAnnotation(ManyToMany.class)) {
      try {
        final SignatureAttribute.TypeArgument target =
            ((SignatureAttribute.ClassType)
                    SignatureAttribute.toFieldSignature(persistentField.getGenericSignature()))
                .getTypeArguments()[0];
        return persistentField.getDeclaringClass().getClassPool().get(target.toString());
      } catch (BadBytecode ignore) {
      }
    }
    return null;
  }

  private void handleCompositeField(
      CtClass managedCtClass, CtField persistentField, CtMethod fieldWriter)
      throws NotFoundException, CannotCompileException {
    if (!persistentField.hasAnnotation(Embedded.class)) {
      return;
    }

    // make sure to add the CompositeOwner interface
    managedCtClass.addInterface(classPool.get(CompositeOwner.class.getName()));

    if (enhancementContext.isCompositeClass(managedCtClass)) {
      // if a composite have a embedded field we need to implement the TRACKER_CHANGER_NAME method
      // as well
      MethodWriter.write(
          managedCtClass,
          ""
              + "public void %1$s(String name) {%n"
              + "  if (%2$s != null) { %2$s.callOwner(\".\" + name) ; }%n}",
          EnhancerConstants.TRACKER_CHANGER_NAME,
          EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME);
    }

    // cleanup previous owner
    fieldWriter.insertBefore(
        String.format(
            "" + "if (%1$s != null) { ((%2$s) %1$s).%3$s(\"%1$s\"); }%n",
            persistentField.getName(),
            CompositeTracker.class.getName(),
            EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER));

    // trigger track changes
    fieldWriter.insertAfter(
        String.format(
            "" + "((%2$s) %1$s).%4$s(\"%1$s\", (%3$s) this);%n" + "%5$s(\"%1$s\");",
            persistentField.getName(),
            CompositeTracker.class.getName(),
            CompositeOwner.class.getName(),
            EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER,
            EnhancerConstants.TRACKER_CHANGER_NAME));
  }

  protected void enhanceAttributesAccess(
      CtClass managedCtClass,
      IdentityHashMap<String, PersistentAttributeAccessMethods> attributeDescriptorMap) {
    final ConstPool constPool = managedCtClass.getClassFile().getConstPool();

    for (Object oMethod : managedCtClass.getClassFile().getMethods()) {
      final MethodInfo methodInfo = (MethodInfo) oMethod;
      final String methodName = methodInfo.getName();

      // skip methods added by enhancement and abstract methods (methods without any code)
      if (methodName.startsWith("$$_hibernate_") || methodInfo.getCodeAttribute() == null) {
        continue;
      }

      try {
        final CodeIterator itr = methodInfo.getCodeAttribute().iterator();
        while (itr.hasNext()) {
          final int index = itr.next();
          final int op = itr.byteAt(index);
          if (op != Opcode.PUTFIELD && op != Opcode.GETFIELD) {
            continue;
          }
          final String fieldName = constPool.getFieldrefName(itr.u16bitAt(index + 1));
          final PersistentAttributeAccessMethods attributeMethods =
              attributeDescriptorMap.get(fieldName);

          // its not a field we have enhanced for interception, so skip it
          if (attributeMethods == null) {
            continue;
          }
          // System.out.printf( "Transforming access to field [%s] from method [%s]%n", fieldName,
          // methodName );
          log.debugf("Transforming access to field [%s] from method [%s]", fieldName, methodName);

          if (op == Opcode.GETFIELD) {
            final int methodIndex = MethodWriter.addMethod(constPool, attributeMethods.getReader());
            itr.writeByte(Opcode.INVOKESPECIAL, index);
            itr.write16bit(methodIndex, index + 1);
          } else {
            final int methodIndex = MethodWriter.addMethod(constPool, attributeMethods.getWriter());
            itr.writeByte(Opcode.INVOKESPECIAL, index);
            itr.write16bit(methodIndex, index + 1);
          }
        }
        methodInfo.getCodeAttribute().setAttribute(MapMaker.make(classPool, methodInfo));
      } catch (BadBytecode bb) {
        final String msg =
            String.format(
                "Unable to perform field access transformation in method [%s]", methodName);
        throw new EnhancementException(msg, bb);
      }
    }
  }

  private static class PersistentAttributeAccessMethods {
    private final CtMethod reader;
    private final CtMethod writer;

    private PersistentAttributeAccessMethods(CtMethod reader, CtMethod writer) {
      this.reader = reader;
      this.writer = writer;
    }

    private CtMethod getReader() {
      return reader;
    }

    private CtMethod getWriter() {
      return writer;
    }
  }

  /**
   * Replace access to fields of entities (for example, entity.field) with a call to the enhanced
   * getter / setter (in this example, entity.$$_hibernate_read_field()). It's assumed that the
   * target entity is enhanced as well.
   *
   * @param managedCtClass Class to enhance
   */
  public void enhanceFieldAccess(CtClass managedCtClass) {
    final ConstPool constPool = managedCtClass.getClassFile().getConstPool();

    for (Object oMethod : managedCtClass.getClassFile().getMethods()) {
      final MethodInfo methodInfo = (MethodInfo) oMethod;
      final String methodName = methodInfo.getName();

      // skip methods added by enhancement and abstract methods (methods without any code)
      if (methodName.startsWith("$$_hibernate_") || methodInfo.getCodeAttribute() == null) {
        continue;
      }

      try {
        final CodeIterator itr = methodInfo.getCodeAttribute().iterator();
        while (itr.hasNext()) {
          int index = itr.next();
          int op = itr.byteAt(index);
          if (op != Opcode.PUTFIELD && op != Opcode.GETFIELD) {
            continue;
          }
          String fieldName = constPool.getFieldrefName(itr.u16bitAt(index + 1));
          String fieldClassName =
              constPool.getClassInfo(constPool.getFieldrefClass(itr.u16bitAt(index + 1)));
          CtClass targetCtClass = this.classPool.getCtClass(fieldClassName);

          if (!enhancementContext.isEntityClass(targetCtClass)
              && !enhancementContext.isCompositeClass(targetCtClass)) {
            continue;
          }
          if (targetCtClass == managedCtClass
              || !enhancementContext.isPersistentField(targetCtClass.getField(fieldName))
              || "this$0".equals(fieldName)) {
            continue;
          }

          log.debugf("Transforming access to field [%s] from method [%s]", fieldName, methodName);

          if (op == Opcode.GETFIELD) {
            int fieldReaderMethodIndex =
                constPool.addMethodrefInfo(
                    constPool.addClassInfo(fieldClassName),
                    EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + fieldName,
                    "()" + constPool.getFieldrefType(itr.u16bitAt(index + 1)));
            itr.writeByte(Opcode.INVOKEVIRTUAL, index);
            itr.write16bit(fieldReaderMethodIndex, index + 1);
          } else {
            int fieldWriterMethodIndex =
                constPool.addMethodrefInfo(
                    constPool.addClassInfo(fieldClassName),
                    EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName,
                    "(" + constPool.getFieldrefType(itr.u16bitAt(index + 1)) + ")V");
            itr.writeByte(Opcode.INVOKEVIRTUAL, index);
            itr.write16bit(fieldWriterMethodIndex, index + 1);
          }
        }
        methodInfo.getCodeAttribute().setAttribute(MapMaker.make(classPool, methodInfo));
      } catch (BadBytecode bb) {
        final String msg =
            String.format(
                "Unable to perform field access transformation in method [%s]", methodName);
        throw new EnhancementException(msg, bb);
      } catch (NotFoundException nfe) {
        final String msg =
            String.format(
                "Unable to perform field access transformation in method [%s]", methodName);
        throw new EnhancementException(msg, nfe);
      }
    }
  }
}
/**
 * The isolation delegate for JDBC {@link Connection} based transactions
 *
 * @author Steve Ebersole
 */
public class JdbcIsolationDelegate implements IsolationDelegate {
  private static final CoreMessageLogger LOG =
      CoreLogging.messageLogger(JdbcIsolationDelegate.class);

  private final TransactionCoordinator transactionCoordinator;

  public JdbcIsolationDelegate(TransactionCoordinator transactionCoordinator) {
    this.transactionCoordinator = transactionCoordinator;
  }

  protected JdbcConnectionAccess jdbcConnectionAccess() {
    return transactionCoordinator.getTransactionContext().getJdbcConnectionAccess();
  }

  protected SqlExceptionHelper sqlExceptionHelper() {
    return transactionCoordinator
        .getJdbcCoordinator()
        .getLogicalConnection()
        .getJdbcServices()
        .getSqlExceptionHelper();
  }

  @Override
  public <T> T delegateWork(WorkExecutorVisitable<T> work, boolean transacted)
      throws HibernateException {
    boolean wasAutoCommit = false;
    try {
      Connection connection = jdbcConnectionAccess().obtainConnection();
      try {
        if (transacted) {
          if (connection.getAutoCommit()) {
            wasAutoCommit = true;
            connection.setAutoCommit(false);
          }
        }

        T result = work.accept(new WorkExecutor<T>(), connection);

        if (transacted) {
          connection.commit();
        }

        return result;
      } catch (Exception e) {
        try {
          if (transacted && !connection.isClosed()) {
            connection.rollback();
          }
        } catch (Exception ignore) {
          LOG.unableToRollbackConnection(ignore);
        }

        if (e instanceof HibernateException) {
          throw (HibernateException) e;
        } else if (e instanceof SQLException) {
          throw sqlExceptionHelper().convert((SQLException) e, "error performing isolated work");
        } else {
          throw new HibernateException("error performing isolated work", e);
        }
      } finally {
        if (transacted && wasAutoCommit) {
          try {
            connection.setAutoCommit(true);
          } catch (Exception ignore) {
            LOG.trace("was unable to reset connection back to auto-commit");
          }
        }
        try {
          jdbcConnectionAccess().releaseConnection(connection);
        } catch (Exception ignore) {
          LOG.unableToReleaseIsolatedConnection(ignore);
        }
      }
    } catch (SQLException sqle) {
      throw sqlExceptionHelper().convert(sqle, "unable to obtain isolated JDBC connection");
    }
  }
}
/**
 * Encapsulates the creation of FromElements and JoinSequences.
 *
 * @author josh
 */
public class FromElementFactory implements SqlTokenTypes {
  private static final CoreMessageLogger LOG = CoreLogging.messageLogger(FromElementFactory.class);

  private FromClause fromClause;
  private FromElement origin;
  private String path;

  private String classAlias;
  private String[] columns;
  private boolean implied;
  private boolean inElementsFunction;
  private boolean collection;
  private QueryableCollection queryableCollection;
  private CollectionType collectionType;

  /** Creates entity from elements. */
  public FromElementFactory(FromClause fromClause, FromElement origin, String path) {
    this.fromClause = fromClause;
    this.origin = origin;
    this.path = path;
    collection = false;
  }

  /** Creates collection from elements. */
  public FromElementFactory(
      FromClause fromClause,
      FromElement origin,
      String path,
      String classAlias,
      String[] columns,
      boolean implied) {
    this(fromClause, origin, path);
    this.classAlias = classAlias;
    this.columns = columns;
    this.implied = implied;
    collection = true;
  }

  FromElement addFromElement() throws SemanticException {
    final FromClause parentFromClause = fromClause.getParentFromClause();
    if (parentFromClause != null) {
      // Look up class name using the first identifier in the path.
      final String pathAlias = PathHelper.getAlias(path);
      final FromElement parentFromElement = parentFromClause.getFromElement(pathAlias);
      if (parentFromElement != null) {
        return createFromElementInSubselect(path, pathAlias, parentFromElement, classAlias);
      }
    }

    final EntityPersister entityPersister =
        fromClause.getSessionFactoryHelper().requireClassPersister(path);

    final FromElement elem =
        createAndAddFromElement(
            path,
            classAlias,
            entityPersister,
            (EntityType) ((Queryable) entityPersister).getType(),
            null);

    // Add to the query spaces.
    fromClause.getWalker().addQuerySpaces(entityPersister.getQuerySpaces());

    return elem;
  }

  private FromElement createFromElementInSubselect(
      String path, String pathAlias, FromElement parentFromElement, String classAlias)
      throws SemanticException {
    LOG.debugf("createFromElementInSubselect() : path = %s", path);

    // Create an DotNode AST for the path and resolve it.
    FromElement fromElement = evaluateFromElementPath(path, classAlias);
    EntityPersister entityPersister = fromElement.getEntityPersister();

    // If the first identifier in the path refers to the class alias (not the class name), then this
    // is a correlated subselect.  If it's a correlated sub-select, use the existing table alias.
    // Otherwise
    // generate a new one.
    String tableAlias = null;
    boolean correlatedSubselect = pathAlias.equals(parentFromElement.getClassAlias());
    if (correlatedSubselect) {
      tableAlias = fromElement.getTableAlias();
    } else {
      tableAlias = null;
    }

    // If the from element isn't in the same clause, create a new from element.
    if (fromElement.getFromClause() != fromClause) {
      LOG.debug("createFromElementInSubselect() : creating a new FROM element...");
      fromElement = createFromElement(entityPersister);
      initializeAndAddFromElement(
          fromElement,
          path,
          classAlias,
          entityPersister,
          (EntityType) ((Queryable) entityPersister).getType(),
          tableAlias);
    }
    LOG.debugf("createFromElementInSubselect() : %s -> %s", path, fromElement);
    return fromElement;
  }

  private FromElement evaluateFromElementPath(String path, String classAlias)
      throws SemanticException {
    ASTFactory factory = fromClause.getASTFactory();
    FromReferenceNode pathNode = (FromReferenceNode) PathHelper.parsePath(path, factory);
    pathNode.recursiveResolve(
        // This is the root level node.
        FromReferenceNode.ROOT_LEVEL,
        // Generate an explicit from clause at the root.
        false,
        classAlias,
        null);
    if (pathNode.getImpliedJoin() != null) {
      return pathNode.getImpliedJoin();
    }
    return pathNode.getFromElement();
  }

  FromElement createCollectionElementsJoin(
      QueryableCollection queryableCollection, String collectionName) throws SemanticException {
    JoinSequence collectionJoinSequence =
        fromClause
            .getSessionFactoryHelper()
            .createCollectionJoinSequence(queryableCollection, collectionName);
    this.queryableCollection = queryableCollection;
    return createCollectionJoin(collectionJoinSequence, null);
  }

  public FromElement createCollection(
      QueryableCollection queryableCollection,
      String role,
      JoinType joinType,
      boolean fetchFlag,
      boolean indexed)
      throws SemanticException {
    if (!collection) {
      throw new IllegalStateException("FromElementFactory not initialized for collections!");
    }
    this.inElementsFunction = indexed;
    FromElement elem;
    this.queryableCollection = queryableCollection;
    collectionType = queryableCollection.getCollectionType();
    String roleAlias = fromClause.getAliasGenerator().createName(role);

    // Correlated subqueries create 'special' implied from nodes
    // because correlated subselects can't use an ANSI-style join
    boolean explicitSubqueryFromElement = fromClause.isSubQuery() && !implied;
    if (explicitSubqueryFromElement) {
      String pathRoot = StringHelper.root(path);
      FromElement origin = fromClause.getFromElement(pathRoot);
      if (origin == null || origin.getFromClause() != fromClause) {
        implied = true;
      }
    }

    // super-duper-classic-parser-regression-testing-mojo-magic...
    if (explicitSubqueryFromElement && PathSeparatorNode.useThetaStyleImplicitJoins) {
      implied = true;
    }

    Type elementType = queryableCollection.getElementType();
    if (elementType.isEntityType()) {
      // A collection of entities...
      elem = createEntityAssociation(role, roleAlias, joinType);
    } else if (elementType.isComponentType()) {
      // A collection of components...
      JoinSequence joinSequence = createJoinSequence(roleAlias, joinType);
      elem = createCollectionJoin(joinSequence, roleAlias);
    } else {
      // A collection of scalar elements...
      JoinSequence joinSequence = createJoinSequence(roleAlias, joinType);
      elem = createCollectionJoin(joinSequence, roleAlias);
    }

    elem.setRole(role);
    elem.setQueryableCollection(queryableCollection);
    // Don't include sub-classes for implied collection joins or subquery joins.
    if (implied) {
      elem.setIncludeSubclasses(false);
    }

    if (explicitSubqueryFromElement) {
      // Treat explict from elements in sub-queries properly.
      elem.setInProjectionList(true);
    }

    if (fetchFlag) {
      elem.setFetch(true);
    }
    return elem;
  }

  public FromElement createEntityJoin(
      String entityClass,
      String tableAlias,
      JoinSequence joinSequence,
      boolean fetchFlag,
      boolean inFrom,
      EntityType type,
      String role,
      String joinPath)
      throws SemanticException {
    FromElement elem = createJoin(entityClass, tableAlias, joinSequence, type, false);
    elem.setFetch(fetchFlag);

    if (joinPath != null) {
      elem.applyTreatAsDeclarations(fromClause.getWalker().getTreatAsDeclarationsByPath(joinPath));
    }

    EntityPersister entityPersister = elem.getEntityPersister();
    int numberOfTables = entityPersister.getQuerySpaces().length;
    if (numberOfTables > 1 && implied && !elem.useFromFragment()) {
      LOG.debug("createEntityJoin() : Implied multi-table entity join");
      elem.setUseFromFragment(true);
    }

    // If this is an implied join in a FROM clause, then use ANSI-style joining, and set the
    // flag on the FromElement that indicates that it was implied in the FROM clause itself.
    if (implied && inFrom) {
      joinSequence.setUseThetaStyle(false);
      elem.setUseFromFragment(true);
      elem.setImpliedInFromClause(true);
    }
    if (elem.getWalker().isSubQuery()) {
      // two conditions where we need to transform this to a theta-join syntax:
      //      1) 'elem' is the "root from-element" in correlated subqueries
      //      2) The DotNode.useThetaStyleImplicitJoins has been set to true
      //          and 'elem' represents an implicit join
      if (elem.getFromClause() != elem.getOrigin().getFromClause()
          ||
          //			        ( implied && DotNode.useThetaStyleImplicitJoins ) ) {
          PathSeparatorNode.useThetaStyleImplicitJoins) {
        // the "root from-element" in correlated subqueries do need this piece
        elem.setType(FROM_FRAGMENT);
        joinSequence.setUseThetaStyle(true);
        elem.setUseFromFragment(false);
      }
    }

    elem.setRole(role);

    return elem;
  }

  public FromElement createComponentJoin(ComponentType type) {

    // need to create a "place holder" from-element that can store the component/alias for this
    // 		component join
    return new ComponentJoin(fromClause, origin, classAlias, path, type);
  }

  FromElement createElementJoin(QueryableCollection queryableCollection) throws SemanticException {
    FromElement elem;

    implied =
        true; // TODO: always true for now, but not if we later decide to support elements() in the
              // from clause
    inElementsFunction = true;
    Type elementType = queryableCollection.getElementType();
    if (!elementType.isEntityType()) {
      throw new IllegalArgumentException(
          "Cannot create element join for a collection of non-entities!");
    }
    this.queryableCollection = queryableCollection;
    SessionFactoryHelper sfh = fromClause.getSessionFactoryHelper();
    FromElement destination = null;
    String tableAlias = null;
    EntityPersister entityPersister = queryableCollection.getElementPersister();
    tableAlias = fromClause.getAliasGenerator().createName(entityPersister.getEntityName());
    String associatedEntityName = entityPersister.getEntityName();
    EntityPersister targetEntityPersister = sfh.requireClassPersister(associatedEntityName);
    // Create the FROM element for the target (the elements of the collection).
    destination =
        createAndAddFromElement(
            associatedEntityName,
            classAlias,
            targetEntityPersister,
            (EntityType) queryableCollection.getElementType(),
            tableAlias);
    // If the join is implied, then don't include sub-classes on the element.
    if (implied) {
      destination.setIncludeSubclasses(false);
    }
    fromClause.addCollectionJoinFromElementByPath(path, destination);
    //		origin.addDestination(destination);
    // Add the query spaces.
    fromClause.getWalker().addQuerySpaces(entityPersister.getQuerySpaces());

    CollectionType type = queryableCollection.getCollectionType();
    String role = type.getRole();
    String roleAlias = origin.getTableAlias();

    String[] targetColumns = sfh.getCollectionElementColumns(role, roleAlias);
    AssociationType elementAssociationType = sfh.getElementAssociationType(type);

    // Create the join element under the from element.
    JoinType joinType = JoinType.INNER_JOIN;
    JoinSequence joinSequence =
        sfh.createJoinSequence(
            implied, elementAssociationType, tableAlias, joinType, targetColumns);
    elem = initializeJoin(path, destination, joinSequence, targetColumns, origin, false);
    elem.setUseFromFragment(
        true); // The associated entity is implied, but it must be included in the FROM.
    elem.setCollectionTableAlias(roleAlias); // The collection alias is the role.
    return elem;
  }

  private FromElement createCollectionJoin(JoinSequence collectionJoinSequence, String tableAlias)
      throws SemanticException {
    String text = queryableCollection.getTableName();
    AST ast = createFromElement(text);
    FromElement destination = (FromElement) ast;
    Type elementType = queryableCollection.getElementType();
    if (elementType.isCollectionType()) {
      throw new SemanticException("Collections of collections are not supported!");
    }
    destination.initializeCollection(fromClause, classAlias, tableAlias);
    destination.setType(JOIN_FRAGMENT); // Tag this node as a JOIN.
    destination.setIncludeSubclasses(false); // Don't include subclasses in the join.
    destination.setCollectionJoin(true); // This is a clollection join.
    destination.setJoinSequence(collectionJoinSequence);
    destination.setOrigin(origin, false);
    destination.setCollectionTableAlias(tableAlias);
    //		origin.addDestination( destination );
    // This was the cause of HHH-242
    //		origin.setType( FROM_FRAGMENT );			// Set the parent node type so that the AST is properly
    // formed.
    origin.setText(""); // The destination node will have all the FROM text.
    origin.setCollectionJoin(
        true); // The parent node is a collection join too (voodoo - see JoinProcessor)
    fromClause.addCollectionJoinFromElementByPath(path, destination);
    fromClause.getWalker().addQuerySpaces(queryableCollection.getCollectionSpaces());
    return destination;
  }

  private FromElement createEntityAssociation(String role, String roleAlias, JoinType joinType)
      throws SemanticException {
    FromElement elem;
    Queryable entityPersister = (Queryable) queryableCollection.getElementPersister();
    String associatedEntityName = entityPersister.getEntityName();
    // Get the class name of the associated entity.
    if (queryableCollection.isOneToMany()) {
      LOG.debugf(
          "createEntityAssociation() : One to many - path = %s role = %s associatedEntityName = %s",
          path, role, associatedEntityName);
      JoinSequence joinSequence = createJoinSequence(roleAlias, joinType);

      elem =
          createJoin(
              associatedEntityName,
              roleAlias,
              joinSequence,
              (EntityType) queryableCollection.getElementType(),
              false);
    } else {
      LOG.debugf(
          "createManyToMany() : path = %s role = %s associatedEntityName = %s",
          path, role, associatedEntityName);
      elem =
          createManyToMany(
              role,
              associatedEntityName,
              roleAlias,
              entityPersister,
              (EntityType) queryableCollection.getElementType(),
              joinType);
      fromClause.getWalker().addQuerySpaces(queryableCollection.getCollectionSpaces());
    }
    elem.setCollectionTableAlias(roleAlias);
    return elem;
  }

  private FromElement createJoin(
      String entityClass,
      String tableAlias,
      JoinSequence joinSequence,
      EntityType type,
      boolean manyToMany)
      throws SemanticException {
    //  origin, path, implied, columns, classAlias,
    EntityPersister entityPersister =
        fromClause.getSessionFactoryHelper().requireClassPersister(entityClass);
    FromElement destination =
        createAndAddFromElement(entityClass, classAlias, entityPersister, type, tableAlias);
    return initializeJoin(path, destination, joinSequence, getColumns(), origin, manyToMany);
  }

  private FromElement createManyToMany(
      String role,
      String associatedEntityName,
      String roleAlias,
      Queryable entityPersister,
      EntityType type,
      JoinType joinType)
      throws SemanticException {
    FromElement elem;
    SessionFactoryHelper sfh = fromClause.getSessionFactoryHelper();
    if (inElementsFunction /*implied*/) {
      // For implied many-to-many, just add the end join.
      JoinSequence joinSequence = createJoinSequence(roleAlias, joinType);
      elem = createJoin(associatedEntityName, roleAlias, joinSequence, type, true);
    } else {
      // For an explicit many-to-many relationship, add a second join from the intermediate
      // (many-to-many) table to the destination table.  Also, make sure that the from element's
      // idea of the destination is the destination table.
      String tableAlias =
          fromClause.getAliasGenerator().createName(entityPersister.getEntityName());
      String[] secondJoinColumns = sfh.getCollectionElementColumns(role, roleAlias);
      // Add the second join, the one that ends in the destination table.
      JoinSequence joinSequence = createJoinSequence(roleAlias, joinType);
      joinSequence.addJoin(
          sfh.getElementAssociationType(collectionType), tableAlias, joinType, secondJoinColumns);
      elem = createJoin(associatedEntityName, tableAlias, joinSequence, type, false);
      elem.setUseFromFragment(true);
    }
    return elem;
  }

  private JoinSequence createJoinSequence(String roleAlias, JoinType joinType) {
    SessionFactoryHelper sessionFactoryHelper = fromClause.getSessionFactoryHelper();
    String[] joinColumns = getColumns();
    if (collectionType == null) {
      throw new IllegalStateException("collectionType is null!");
    }
    return sessionFactoryHelper.createJoinSequence(
        implied, collectionType, roleAlias, joinType, joinColumns);
  }

  private FromElement createAndAddFromElement(
      String className,
      String classAlias,
      EntityPersister entityPersister,
      EntityType type,
      String tableAlias) {
    if (!(entityPersister instanceof Joinable)) {
      throw new IllegalArgumentException(
          "EntityPersister " + entityPersister + " does not implement Joinable!");
    }
    FromElement element = createFromElement(entityPersister);
    initializeAndAddFromElement(element, className, classAlias, entityPersister, type, tableAlias);
    return element;
  }

  private void initializeAndAddFromElement(
      FromElement element,
      String className,
      String classAlias,
      EntityPersister entityPersister,
      EntityType type,
      String tableAlias) {
    if (tableAlias == null) {
      AliasGenerator aliasGenerator = fromClause.getAliasGenerator();
      tableAlias = aliasGenerator.createName(entityPersister.getEntityName());
    }
    element.initializeEntity(fromClause, className, entityPersister, type, classAlias, tableAlias);
  }

  private FromElement createFromElement(EntityPersister entityPersister) {
    Joinable joinable = (Joinable) entityPersister;
    String text = joinable.getTableName();
    AST ast = createFromElement(text);
    FromElement element = (FromElement) ast;
    return element;
  }

  private AST createFromElement(String text) {
    AST ast =
        ASTUtil.create(
            fromClause.getASTFactory(),
            implied
                ? IMPLIED_FROM
                : FROM_FRAGMENT, // This causes the factory to instantiate the desired class.
            text);
    // Reset the node type, because the rest of the system is expecting FROM_FRAGMENT, all we wanted
    // was
    // for the factory to create the right sub-class.  This might get reset again later on anyway to
    // make the
    // SQL generation simpler.
    ast.setType(FROM_FRAGMENT);
    return ast;
  }

  private FromElement initializeJoin(
      String path,
      FromElement destination,
      JoinSequence joinSequence,
      String[] columns,
      FromElement origin,
      boolean manyToMany) {
    destination.setType(JOIN_FRAGMENT);
    destination.setJoinSequence(joinSequence);
    destination.setColumns(columns);
    destination.setOrigin(origin, manyToMany);
    fromClause.addJoinByPathMap(path, destination);
    return destination;
  }

  private String[] getColumns() {
    if (columns == null) {
      throw new IllegalStateException("No foriegn key columns were supplied!");
    }
    return columns;
  }
}