/**
   * Utility to create the datastore identity column and mapping. This is used in 2 modes. The first
   * is where we have a (primary) class table and we aren't creating the OID mapping as a FK to
   * another class. The second is where we have a (secondary) class table and we are creating the
   * OID mapping as a FK to the primary class. In the second case the refTable will be specified.
   *
   * @param columnMetaData The column MetaData for the datastore id
   * @param refTable Table used as a reference (if any)
   * @param cmd The MetaData for the class
   */
  void addDatastoreId(
      ColumnMetaData columnMetaData, DatastoreClass refTable, AbstractClassMetaData cmd) {
    // Create the mapping, setting its table
    datastoreIDMapping = new DatastoreIdMapping();
    datastoreIDMapping.setTable(this);
    datastoreIDMapping.initialize(storeMgr, cmd.getFullClassName());

    // Create a ColumnMetaData in the container if none is defined
    ColumnMetaData colmd = null;
    if (columnMetaData == null) {
      colmd = new ColumnMetaData();
    } else {
      colmd = columnMetaData;
    }
    if (colmd.getName() == null) {
      // Provide default column naming if none is defined
      if (refTable != null) {
        colmd.setName(
            storeMgr
                .getIdentifierFactory()
                .newColumnIdentifier(
                    refTable.getIdentifier().getName(),
                    this.storeMgr
                        .getNucleusContext()
                        .getTypeManager()
                        .isDefaultEmbeddedType(DatastoreId.class),
                    FieldRole.ROLE_OWNER,
                    false)
                .getName());
      } else {
        colmd.setName(
            storeMgr
                .getIdentifierFactory()
                .newColumnIdentifier(
                    identifier.getName(),
                    this.storeMgr
                        .getNucleusContext()
                        .getTypeManager()
                        .isDefaultEmbeddedType(DatastoreId.class),
                    FieldRole.ROLE_NONE,
                    false)
                .getName());
      }
    }

    // Add the datastore identity column as the PK
    Column idColumn =
        addColumn(
            DatastoreId.class.getName(),
            storeMgr.getIdentifierFactory().newIdentifier(IdentifierType.COLUMN, colmd.getName()),
            datastoreIDMapping,
            colmd);
    idColumn.setPrimaryKey();

    // Set the identity column type based on the IdentityStrategy
    String strategyName = cmd.getIdentityMetaData().getValueStrategy().toString();
    if (cmd.getIdentityMetaData().getValueStrategy().equals(IdentityStrategy.CUSTOM)) {
      strategyName = cmd.getIdentityMetaData().getValueStrategy().getCustomName();
    }
    if (strategyName != null && IdentityStrategy.NATIVE.toString().equals(strategyName)) {
      strategyName = storeMgr.getStrategyForNative(cmd, -1);
    }

    // Check the value generator type being stored
    Class valueGeneratedType = Long.class;
    if (strategyName != null && IdentityStrategy.IDENTITY.toString().equals(strategyName)) {
      valueGeneratedType = dba.getAutoIncrementJavaTypeForType(valueGeneratedType);
      if (valueGeneratedType != Long.class) {
        NucleusLogger.DATASTORE_SCHEMA.debug(
            "Class "
                + cmd.getFullClassName()
                + " uses IDENTITY strategy and rather than using BIGINT "
                + " for the column type, using "
                + valueGeneratedType.getName()
                + " since the datastore requires that");
      }
    }
    try {
      // Create generator so we can find the generated type
      // a). Try as unique generator first
      AbstractGenerator generator =
          (AbstractGenerator)
              storeMgr
                  .getNucleusContext()
                  .getPluginManager()
                  .createExecutableExtension(
                      "org.datanucleus.store_valuegenerator",
                      new String[] {"name", "unique"},
                      new String[] {strategyName, "true"},
                      "class-name",
                      new Class[] {String.class, Properties.class},
                      new Object[] {null, null});
      if (generator == null) {
        // b). Try as datastore-specific generator
        generator =
            (AbstractGenerator)
                storeMgr
                    .getNucleusContext()
                    .getPluginManager()
                    .createExecutableExtension(
                        "org.datanucleus.store_valuegenerator",
                        new String[] {"name", "datastore"},
                        new String[] {strategyName, storeMgr.getStoreManagerKey()},
                        "class-name",
                        new Class[] {String.class, Properties.class},
                        new Object[] {null, null});
      }
      try {
        if (generator != null) {
          ParameterizedType parameterizedType =
              (ParameterizedType) generator.getClass().getGenericSuperclass();
          valueGeneratedType = (Class) parameterizedType.getActualTypeArguments()[0];
          if (valueGeneratedType == null) {
            // Use getStorageClass method if available
            valueGeneratedType =
                (Class) generator.getClass().getMethod("getStorageClass").invoke(null);
          }
        }
      } catch (Exception e) {
      }
    } catch (Exception e) {
      NucleusLogger.VALUEGENERATION.warn(
          "Error obtaining generator for strategy=" + strategyName, e);
    }

    storeMgr
        .getMappingManager()
        .createDatastoreMapping(datastoreIDMapping, idColumn, valueGeneratedType.getName());
    logMapping("DATASTORE_ID", datastoreIDMapping);

    // Handle any auto-increment requirement
    if (isObjectIdDatastoreAttributed()) {
      if (this instanceof DatastoreClass && ((DatastoreClass) this).isBaseDatastoreClass()) {
        // Only the base class can be autoincremented
        idColumn.setIdentity(true);
      }
    }

    // Check if auto-increment and that it is supported by this RDBMS
    if (idColumn.isIdentity() && !dba.supportsOption(DatastoreAdapter.IDENTITY_COLUMNS)) {
      throw new NucleusException(
              Localiser.msg("057020", cmd.getFullClassName(), "datastore-identity"))
          .setFatal();
    }
  }
  /**
   * Utility to create the application identity columns and mapping. Uses the id mapping of the
   * specified class table and copies the mappings and columns, whilst retaining the passed
   * preferences for column namings. This is used to copy the PK mappings of a superclass table so
   * we have the same PK.
   *
   * @param columnContainer The container of column MetaData with any namings
   * @param refTable The table that we use as reference
   * @param clr The ClassLoaderResolver
   * @param cmd The ClassMetaData
   */
  final void addApplicationIdUsingClassTableId(
      ColumnMetaDataContainer columnContainer,
      DatastoreClass refTable,
      ClassLoaderResolver clr,
      AbstractClassMetaData cmd) {
    ColumnMetaData[] userdefinedCols = null;
    int nextUserdefinedCol = 0;
    if (columnContainer != null) {
      userdefinedCols = columnContainer.getColumnMetaData();
    }

    pkMappings = new JavaTypeMapping[cmd.getPKMemberPositions().length];
    for (int i = 0; i < cmd.getPKMemberPositions().length; i++) {
      AbstractMemberMetaData mmd =
          cmd.getMetaDataForManagedMemberAtAbsolutePosition(cmd.getPKMemberPositions()[i]);
      JavaTypeMapping mapping = refTable.getMemberMapping(mmd);
      if (mapping == null) {
        // probably due to invalid metadata defined by the user
        throw new NucleusUserException(
            "Cannot find mapping for field "
                + mmd.getFullFieldName()
                + " in table "
                + refTable.toString()
                + " "
                + StringUtils.collectionToString(refTable.getColumns()));
      }

      JavaTypeMapping masterMapping =
          storeMgr.getMappingManager().getMapping(clr.classForName(mapping.getType()));
      masterMapping.setMemberMetaData(mmd); // Update field info in mapping
      masterMapping.setTable(this);
      pkMappings[i] = masterMapping;

      // Loop through each id column in the reference table and add the same here
      // applying the required names from the columnContainer
      for (int j = 0; j < mapping.getNumberOfDatastoreMappings(); j++) {
        JavaTypeMapping m = masterMapping;
        Column refColumn = mapping.getDatastoreMapping(j).getColumn();
        if (mapping instanceof PersistableMapping) {
          m =
              storeMgr
                  .getMappingManager()
                  .getMapping(clr.classForName(refColumn.getJavaTypeMapping().getType()));
          ((PersistableMapping) masterMapping).addJavaTypeMapping(m);
        }

        ColumnMetaData userdefinedColumn = null;
        if (userdefinedCols != null) {
          for (int k = 0; k < userdefinedCols.length; k++) {
            if (refColumn.getIdentifier().toString().equals(userdefinedCols[k].getTarget())) {
              userdefinedColumn = userdefinedCols[k];
              break;
            }
          }
          if (userdefinedColumn == null && nextUserdefinedCol < userdefinedCols.length) {
            userdefinedColumn = userdefinedCols[nextUserdefinedCol++];
          }
        }

        // Add this application identity column
        Column idColumn = null;
        if (userdefinedColumn != null) {
          // User has provided a name for this column
          // Currently we only use the column namings from the users definition but we could easily
          // take more of their details.
          idColumn =
              addColumn(
                  refColumn.getStoredJavaType(),
                  storeMgr
                      .getIdentifierFactory()
                      .newIdentifier(IdentifierType.COLUMN, userdefinedColumn.getName()),
                  m,
                  refColumn.getColumnMetaData());
        } else {
          // No name provided so take same as superclass
          idColumn =
              addColumn(
                  refColumn.getStoredJavaType(),
                  refColumn.getIdentifier(),
                  m,
                  refColumn.getColumnMetaData());
        }
        if (mapping.getDatastoreMapping(j).getColumn().getColumnMetaData() != null) {
          refColumn.copyConfigurationTo(idColumn);
        }
        idColumn.setPrimaryKey();

        // Set the column type based on the field.getType()
        getStoreManager()
            .getMappingManager()
            .createDatastoreMapping(m, idColumn, refColumn.getJavaTypeMapping().getType());
      }

      // Update highest field number if this is higher
      int absoluteFieldNumber = mmd.getAbsoluteFieldNumber();
      if (absoluteFieldNumber > highestMemberNumber) {
        highestMemberNumber = absoluteFieldNumber;
      }
    }
  }