/** 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));
    }
  }
  /**
   * 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();
    }
  }
  /**
   * Constructs jdbc entity meta data defined in the jdbcApplication and with the data from the
   * entity meta data which is loaded from the ejb-jar.xml file.
   *
   * @param jdbcApplication the application in which this entity is defined
   * @param entity the entity meta data for this entity that is loaded from the ejb-jar.xml file
   * @throws DeploymentException if an problem occures while loading the classes or if data in the
   *     ejb-jar.xml is inconsistent with data from jbosscmp-jdbc.xml file
   */
  public JDBCEntityMetaData(JDBCApplicationMetaData jdbcApplication, EntityMetaData entity)
      throws DeploymentException {
    this.jdbcApplication = jdbcApplication;
    entityName = entity.getEjbName();
    listCacheMax = 1000;
    fetchSize = 0;

    try {
      entityClass = getClassLoader().loadClass(entity.getEjbClass());
    } catch (ClassNotFoundException e) {
      throw new DeploymentException("entity class not found for ejb-name: " + entityName, e);
    }

    try {
      primaryKeyClass = getClassLoader().loadClass(entity.getPrimaryKeyClass());
    } catch (ClassNotFoundException e) {
      throw new DeploymentException(
          "could not load primary key class: " + entity.getPrimaryKeyClass());
    }

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

    primaryKeyFieldName = entity.getPrimKeyField();

    String home = entity.getHome();
    if (home != null) {
      try {
        homeClass = getClassLoader().loadClass(home);
      } catch (ClassNotFoundException e) {
        throw new DeploymentException("home class not found: " + home);
      }
      try {
        remoteClass = getClassLoader().loadClass(entity.getRemote());
      } catch (ClassNotFoundException e) {
        throw new DeploymentException("remote class not found: " + entity.getRemote());
      }
    } else {
      homeClass = null;
      remoteClass = null;
    }

    String localHome = entity.getLocalHome();
    if (localHome != null) {
      try {
        localHomeClass = getClassLoader().loadClass(localHome);
      } catch (ClassNotFoundException e) {
        throw new DeploymentException("local home class not found: " + localHome);
      }
      try {
        localClass = getClassLoader().loadClass(entity.getLocal());
      } catch (ClassNotFoundException e) {
        throw new DeploymentException("local class not found: " + entity.getLocal());
      }
    } else {
      // we must have a home or local home
      if (home == null) {
        throw new DeploymentException(
            "Entity must have atleast a home " + "or local home: " + entityName);
      }

      localHomeClass = null;
      localClass = null;
    }

    // we replace the . by _ because some dbs die on it...
    // the table name may be overridden in importXml(jbosscmp-jdbc.xml)
    tableName = entityName.replace('.', '_');

    // Warn: readTimeOut should be setup before cmp fields are created
    // otherwise readTimeOut in cmp fields will be set to 0 by default
    dataSourceName = null;
    datasourceMappingName = null;
    datasourceMapping = null;
    createTable = false;
    removeTable = false;
    alterTable = false;
    rowLocking = false;
    primaryKeyConstraint = false;
    readOnly = false;
    readTimeOut = -1;
    tablePostCreateCmd = null;
    qlCompiler = null;
    throwRuntimeExceptions = false;

    // build the metadata for the cmp fields now in case there is
    // no jbosscmp-jdbc.xml
    List nonPkFieldNames = new ArrayList();
    for (Iterator i = entity.getCMPFields(); i.hasNext(); ) {
      String cmpFieldName = (String) i.next();
      JDBCCMPFieldMetaData cmpField = new JDBCCMPFieldMetaData(this, cmpFieldName);
      cmpFields.add(cmpField);
      cmpFieldsByName.put(cmpFieldName, cmpField);
      if (!cmpField.isPrimaryKeyMember()) {
        nonPkFieldNames.add(cmpFieldName);
      }
    }

    // AL: add unknown primary key if primaryKeyClass is Object
    // AL: this is set up only in this constructor
    // AL: because, AFAIK, others are called with default value
    // AL: produced by this one
    if (primaryKeyClass == java.lang.Object.class) {
      JDBCCMPFieldMetaData upkField = new JDBCCMPFieldMetaData(this);
      cmpFields.add(upkField);
      cmpFieldsByName.put(upkField.getFieldName(), upkField);
    }

    // set eager load fields to all group.
    eagerLoadGroup = "*";

    // Create no lazy load groups. By default every thing is eager loaded.
    // build the metadata for the queries now in case there is no
    // jbosscmp-jdbc.xml
    queryFactory = new JDBCQueryMetaDataFactory(this);

    for (Iterator queriesIterator = entity.getQueries(); queriesIterator.hasNext(); ) {
      QueryMetaData queryData = (QueryMetaData) queriesIterator.next();
      Map newQueries = queryFactory.createJDBCQueryMetaData(queryData);
      // overrides defaults added above
      queries.putAll(newQueries);
    }

    // Create no relationship roles for this entity, will be added
    // by the relation meta data

    readAhead = JDBCReadAheadMetaData.DEFAULT;
    cleanReadAheadOnLoad = false;
    entityCommand = null;
    optimisticLocking = null;
    audit = null;
  }