/**
   * 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;
  }