/** Loads the load groups of cmp fields from the xml element */
  private void loadLoadGroupsXml(Element element) throws DeploymentException {

    Element loadGroupsElement = MetaData.getOptionalChild(element, "load-groups");
    if (loadGroupsElement == null) {
      // no info, default work already done in constructor
      return;
    }

    // only allowed for cmp 2.x
    if (isCMP1x) {
      throw new DeploymentException("load-groups are only allowed " + "for CMP 2.x");
    }

    // load each group
    Iterator groups = MetaData.getChildrenByTagName(loadGroupsElement, "load-group");
    while (groups.hasNext()) {
      Element groupElement = (Element) groups.next();

      // get the load-group-name
      String loadGroupName = MetaData.getUniqueChildContent(groupElement, "load-group-name");
      if (loadGroups.containsKey(loadGroupName)) {
        throw new DeploymentException(
            "Load group already defined: " + " load-group-name=" + loadGroupName);
      }
      if (loadGroupName.equals("*")) {
        throw new DeploymentException(
            "The * load group is automatically " + "defined and can't be overriden");
      }
      ArrayList group = new ArrayList();

      // add each field
      Iterator fields = MetaData.getChildrenByTagName(groupElement, "field-name");
      while (fields.hasNext()) {
        String fieldName = MetaData.getElementContent((Element) fields.next());

        // check if the field is a cmp field that it is not a pk memeber
        JDBCCMPFieldMetaData field = getCMPFieldByName(fieldName);
        if (field != null && field.isPrimaryKeyMember()) {
          throw new DeploymentException(
              "Primary key fields can not be"
                  + " a member of a load group: "
                  + " load-group-name="
                  + loadGroupName
                  + " field-name="
                  + fieldName);
        }

        group.add(fieldName);
      }

      loadGroups.put(loadGroupName, Collections.unmodifiableList(group));
    }
  }
  /** Loads the list of lazy load groups from the xml element. */
  private void loadLazyLoadGroupsXml(Element element) throws DeploymentException {
    Element lazyLoadGroupsElement = MetaData.getOptionalChild(element, "lazy-load-groups");

    // If no info, we're done. Default work was already done in constructor.
    if (lazyLoadGroupsElement == null) {
      return;
    }

    // only allowed for cmp 2.x
    if (isCMP1x) {
      throw new DeploymentException("lazy-load-groups is only allowed for CMP 2.x");
    }

    // get the fields
    Iterator loadGroupNames =
        MetaData.getChildrenByTagName(lazyLoadGroupsElement, "load-group-name");
    while (loadGroupNames.hasNext()) {
      String loadGroupName = MetaData.getElementContent((Element) loadGroupNames.next());
      if (!loadGroupName.equals("*") && !loadGroups.containsKey(loadGroupName)) {
        throw new DeploymentException(
            "Lazy load group not found: " + "load-group-name=" + loadGroupName);
      }

      lazyLoadGroups.add(loadGroupName);
    }
  }
  /**
   * Constructs entity meta data with the data contained in the entity xml element from a
   * jbosscmp-jdbc xml file. Optional values of the xml element that are not present are loaded from
   * the defalutValues parameter.
   *
   * @param jdbcApplication the application in which this entity is defined
   * @param element the xml Element which contains the metadata about this entity
   * @param defaultValues the JDBCEntityMetaData which contains the values for optional elements of
   *     the element
   * @throws DeploymentException if the xml element is not semantically correct
   */
  public JDBCEntityMetaData(
      JDBCApplicationMetaData jdbcApplication, Element element, JDBCEntityMetaData defaultValues)
      throws DeploymentException {
    // store passed in application... application in defaultValues may
    // be different because jdbcApplication is imutable
    this.jdbcApplication = jdbcApplication;

    // set default values
    entityName = defaultValues.getName();
    entityClass = defaultValues.getEntityClass();
    primaryKeyClass = defaultValues.getPrimaryKeyClass();
    isCMP1x = defaultValues.isCMP1x;
    primaryKeyFieldName = defaultValues.getPrimaryKeyFieldName();
    homeClass = defaultValues.getHomeClass();
    remoteClass = defaultValues.getRemoteClass();
    localHomeClass = defaultValues.getLocalHomeClass();
    localClass = defaultValues.getLocalClass();
    queryFactory = new JDBCQueryMetaDataFactory(this);

    if (isCMP1x) {
      abstractSchemaName =
          (defaultValues.getAbstractSchemaName() == null
              ? entityName
              : defaultValues.getAbstractSchemaName());
    } else {
      abstractSchemaName = defaultValues.getAbstractSchemaName();
    }

    // datasource name
    String dataSourceNameString = MetaData.getOptionalChildContent(element, "datasource");
    if (dataSourceNameString != null) {
      dataSourceName = dataSourceNameString;
    } else {
      dataSourceName = defaultValues.getDataSourceName();
    }

    // get the datasource mapping for this datasource (optional, but always
    // set in standardjbosscmp-jdbc.xml)
    String datasourceMappingString =
        MetaData.getOptionalChildContent(element, "datasource-mapping");
    if (datasourceMappingString != null) {
      datasourceMappingName = datasourceMappingString;
      datasourceMapping = jdbcApplication.getTypeMappingByName(datasourceMappingString);
      if (datasourceMapping == null) {
        throw new DeploymentException(
            "Error in jbosscmp-jdbc.xml : "
                + "datasource-mapping "
                + datasourceMappingString
                + " not found");
      }
    } else if (defaultValues.datasourceMappingName != null
        && defaultValues.datasourceMapping != null) {
      datasourceMappingName = null;
      datasourceMapping = defaultValues.datasourceMapping;
    } else {
      datasourceMappingName = null;
      datasourceMapping = obtainTypeMappingFromLibrary(dataSourceName);
    }

    // get table name
    String tableStr = MetaData.getOptionalChildContent(element, "table-name");
    if (tableStr != null) {
      tableName = tableStr;
    } else {
      tableName = defaultValues.getDefaultTableName();
    }

    // create table?  If not provided, keep default.
    String createStr = MetaData.getOptionalChildContent(element, "create-table");
    if (createStr != null) {
      createTable = Boolean.valueOf(createStr).booleanValue();
    } else {
      createTable = defaultValues.getCreateTable();
    }

    // remove table?  If not provided, keep default.
    String removeStr = MetaData.getOptionalChildContent(element, "remove-table");
    if (removeStr != null) {
      removeTable = Boolean.valueOf(removeStr).booleanValue();
    } else {
      removeTable = defaultValues.getRemoveTable();
    }

    // alter table?  If not provided, keep default.
    String alterStr = MetaData.getOptionalChildContent(element, "alter-table");
    if (alterStr != null) {
      alterTable = Boolean.valueOf(alterStr).booleanValue();
    } else {
      alterTable = defaultValues.getAlterTable();
    }

    // get the SQL command to execute after table creation
    Element posttc = MetaData.getOptionalChild(element, "post-table-create");
    if (posttc != null) {
      Iterator it = MetaData.getChildrenByTagName(posttc, "sql-statement");
      tablePostCreateCmd = new ArrayList();
      while (it.hasNext()) {
        Element etmp = (Element) it.next();
        tablePostCreateCmd.add(MetaData.getElementContent(etmp));
      }
    } else {
      tablePostCreateCmd = defaultValues.getDefaultTablePostCreateCmd();
    }

    // read-only
    String readOnlyStr = MetaData.getOptionalChildContent(element, "read-only");
    if (readOnlyStr != null) {
      readOnly = Boolean.valueOf(readOnlyStr).booleanValue();
    } else {
      readOnly = defaultValues.isReadOnly();
    }

    // read-time-out
    String readTimeOutStr = MetaData.getOptionalChildContent(element, "read-time-out");
    if (readTimeOutStr != null) {
      try {
        readTimeOut = Integer.parseInt(readTimeOutStr);
      } catch (NumberFormatException e) {
        throw new DeploymentException(
            "Invalid number format in " + "read-time-out '" + readTimeOutStr + "': " + e);
      }
    } else {
      readTimeOut = defaultValues.getReadTimeOut();
    }

    String sForUpStr = MetaData.getOptionalChildContent(element, "row-locking");
    if (sForUpStr != null) {
      rowLocking = !isReadOnly() && (Boolean.valueOf(sForUpStr).booleanValue());
    } else {
      rowLocking = defaultValues.hasRowLocking();
    }

    // primary key constraint?  If not provided, keep default.
    String pkStr = MetaData.getOptionalChildContent(element, "pk-constraint");
    if (pkStr != null) {
      primaryKeyConstraint = Boolean.valueOf(pkStr).booleanValue();
    } else {
      primaryKeyConstraint = defaultValues.hasPrimaryKeyConstraint();
    }

    // list-cache-max
    String listCacheMaxStr = MetaData.getOptionalChildContent(element, "list-cache-max");
    if (listCacheMaxStr != null) {
      try {
        listCacheMax = Integer.parseInt(listCacheMaxStr);
      } catch (NumberFormatException e) {
        throw new DeploymentException(
            "Invalid number format in read-"
                + "ahead list-cache-max '"
                + listCacheMaxStr
                + "': "
                + e);
      }
      if (listCacheMax < 0) {
        throw new DeploymentException(
            "Negative value for read ahead " + "list-cache-max '" + listCacheMaxStr + "'.");
      }
    } else {
      listCacheMax = defaultValues.getListCacheMax();
    }

    // fetch-size
    String fetchSizeStr = MetaData.getOptionalChildContent(element, "fetch-size");
    if (fetchSizeStr != null) {
      try {
        fetchSize = Integer.parseInt(fetchSizeStr);
      } catch (NumberFormatException e) {
        throw new DeploymentException(
            "Invalid number format in " + "fetch-size '" + fetchSizeStr + "': " + e);
      }
      if (fetchSize < 0) {
        throw new DeploymentException(
            "Negative value for fetch size " + "fetch-size '" + fetchSizeStr + "'.");
      }
    } else {
      fetchSize = defaultValues.getFetchSize();
    }

    String compiler = MetaData.getOptionalChildContent(element, "ql-compiler");
    if (compiler == null) {
      qlCompiler = defaultValues.qlCompiler;
    } else {
      try {
        qlCompiler = GetTCLAction.getContextClassLoader().loadClass(compiler);
      } catch (ClassNotFoundException e) {
        throw new DeploymentException("Failed to load compiler implementation: " + compiler);
      }
    }

    // throw runtime exceptions ?  If not provided, keep default.
    String throwRuntimeExceptionsStr =
        MetaData.getOptionalChildContent(element, "throw-runtime-exceptions");
    if (throwRuntimeExceptionsStr != null) {
      throwRuntimeExceptions = Boolean.valueOf(throwRuntimeExceptionsStr).booleanValue();
    } else {
      throwRuntimeExceptions = defaultValues.getThrowRuntimeExceptions();
    }

    //
    // cmp fields
    //

    // update all existing queries with the new read ahead value
    for (Iterator cmpFieldIterator = defaultValues.cmpFields.iterator();
        cmpFieldIterator.hasNext(); ) {

      JDBCCMPFieldMetaData cmpField =
          new JDBCCMPFieldMetaData(this, (JDBCCMPFieldMetaData) cmpFieldIterator.next());
      cmpFields.add(cmpField);
      cmpFieldsByName.put(cmpField.getFieldName(), cmpField);
    }

    // apply new configurations to the cmpfields
    for (Iterator i = MetaData.getChildrenByTagName(element, "cmp-field"); i.hasNext(); ) {
      Element cmpFieldElement = (Element) i.next();
      String fieldName = MetaData.getUniqueChildContent(cmpFieldElement, "field-name");

      JDBCCMPFieldMetaData oldCMPField = (JDBCCMPFieldMetaData) cmpFieldsByName.get(fieldName);
      if (oldCMPField == null) {
        throw new DeploymentException("CMP field not found : fieldName=" + fieldName);
      }
      JDBCCMPFieldMetaData cmpFieldMetaData =
          new JDBCCMPFieldMetaData(this, cmpFieldElement, oldCMPField);

      // replace the old cmp meta data with the new
      cmpFieldsByName.put(fieldName, cmpFieldMetaData);
      int index = cmpFields.indexOf(oldCMPField);
      cmpFields.remove(oldCMPField);
      cmpFields.add(index, cmpFieldMetaData);
    }

    // unknown primary key field
    if (primaryKeyClass == java.lang.Object.class) {
      Element upkElement = MetaData.getOptionalChild(element, "unknown-pk");
      if (upkElement != null) {
        // assume now there is only one upk field
        JDBCCMPFieldMetaData oldUpkField = null;
        for (Iterator iter = cmpFields.iterator(); iter.hasNext(); ) {
          JDBCCMPFieldMetaData cmpField = (JDBCCMPFieldMetaData) iter.next();
          if (cmpField.isUnknownPkField()) {
            oldUpkField = cmpField;
            break;
          }
        }

        // IMO, this is a redundant check
        if (oldUpkField == null) {
          oldUpkField = new JDBCCMPFieldMetaData(this);
        }

        JDBCCMPFieldMetaData upkField = new JDBCCMPFieldMetaData(this, upkElement, oldUpkField);

        // remove old upk field
        cmpFieldsByName.remove(oldUpkField.getFieldName());
        cmpFieldsByName.put(upkField.getFieldName(), upkField);

        int oldUpkFieldInd = cmpFields.indexOf(oldUpkField);
        cmpFields.remove(oldUpkField);
        cmpFields.add(oldUpkFieldInd, upkField);
      }
    }

    // load-loads
    loadGroups.putAll(defaultValues.loadGroups);
    loadLoadGroupsXml(element);

    // eager-load
    Element eagerLoadGroupElement = MetaData.getOptionalChild(element, "eager-load-group");
    if (eagerLoadGroupElement != null) {
      String eagerLoadGroupTmp = MetaData.getElementContent(eagerLoadGroupElement);
      if (eagerLoadGroupTmp != null && eagerLoadGroupTmp.trim().length() == 0) {
        eagerLoadGroupTmp = null;
      }
      if (eagerLoadGroupTmp != null
          && !eagerLoadGroupTmp.equals("*")
          && !loadGroups.containsKey(eagerLoadGroupTmp)) {
        throw new DeploymentException(
            "Eager load group not found: " + "eager-load-group=" + eagerLoadGroupTmp);
      }
      eagerLoadGroup = eagerLoadGroupTmp;
    } else {
      eagerLoadGroup = defaultValues.getEagerLoadGroup();
    }

    // lazy-loads
    lazyLoadGroups.addAll(defaultValues.lazyLoadGroups);
    loadLazyLoadGroupsXml(element);

    // read-ahead
    Element readAheadElement = MetaData.getOptionalChild(element, "read-ahead");
    if (readAheadElement != null) {
      readAhead = new JDBCReadAheadMetaData(readAheadElement, defaultValues.getReadAhead());
    } else {
      readAhead = defaultValues.readAhead;
    }

    String value = MetaData.getOptionalChildContent(element, "clean-read-ahead-on-load");
    if ("true".equalsIgnoreCase(value)) {
      cleanReadAheadOnLoad = true;
    } else if ("false".equalsIgnoreCase(value)) {
      cleanReadAheadOnLoad = false;
    } else if (value == null) {
      cleanReadAheadOnLoad = defaultValues.cleanReadAheadOnLoad;
    } else {
      throw new DeploymentException(
          "Failed to deploy "
              + entityName
              + ": allowed values for clean-read-ahead-on-load are true and false but got "
              + value);
    }

    // optimistic locking group
    Element optimisticLockingEl = MetaData.getOptionalChild(element, "optimistic-locking");
    if (optimisticLockingEl != null) {
      optimisticLocking = new JDBCOptimisticLockingMetaData(this, optimisticLockingEl);
    } else {
      optimisticLocking = defaultValues.getOptimisticLocking();
    }

    // audit
    Element auditElement = MetaData.getOptionalChild(element, "audit");
    if (auditElement != null) {
      audit = new JDBCAuditMetaData(this, auditElement);
    } else {
      audit = defaultValues.getAudit();
    }

    // queries

    // update all existing queries with the new read ahead value
    for (Iterator queriesIterator = defaultValues.queries.values().iterator();
        queriesIterator.hasNext(); ) {
      JDBCQueryMetaData query =
          JDBCQueryMetaDataFactory.createJDBCQueryMetaData(
              (JDBCQueryMetaData) queriesIterator.next(), readAhead, qlCompiler);
      queries.put(query.getMethod(), query);
    }

    // apply new configurations to the queries
    for (Iterator queriesIterator = MetaData.getChildrenByTagName(element, "query");
        queriesIterator.hasNext(); ) {
      Element queryElement = (Element) queriesIterator.next();
      Map newQueries =
          queryFactory.createJDBCQueryMetaData(queryElement, defaultValues.queries, readAhead);

      // overrides defaults added above
      queries.putAll(newQueries);
    }

    // get the entity command for this entity
    Element entityCommandEl = MetaData.getOptionalChild(element, "entity-command");
    if (entityCommandEl != null) {
      // command name in xml
      String entityCommandName = entityCommandEl.getAttribute("name");

      // default entity command
      // The logic to assign the default value:
      // - if entity-command isn't specified in the entity element,
      //   then it is assigned from the defaults;
      // - if command name in entity equals command name in defaults
      //   then it is assigned from the defaults
      // - else try to find a command in entity-commands with the command
      //   name specified in the entity.
      //   if the match is found it'll be the default, else default is null
      JDBCEntityCommandMetaData defaultEntityCommand = defaultValues.getEntityCommand();

      if ((defaultEntityCommand == null)
          || (!entityCommandName.equals(defaultEntityCommand.getCommandName()))) {
        defaultEntityCommand = jdbcApplication.getEntityCommandByName(entityCommandName);
      }

      if (defaultEntityCommand != null) {
        entityCommand = new JDBCEntityCommandMetaData(entityCommandEl, defaultEntityCommand);
      } else {
        entityCommand = new JDBCEntityCommandMetaData(entityCommandEl);
      }
    } else {
      entityCommand = defaultValues.getEntityCommand();
    }
  }