org.apache.openjpa.persistence.jdbc.XMLPersistenceMappingParser.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.openjpa.persistence.jdbc.XMLPersistenceMappingParser.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.    
 */
package org.apache.openjpa.persistence.jdbc;

import java.lang.reflect.Modifier;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.DiscriminatorType;
import javax.persistence.EnumType;
import javax.persistence.InheritanceType;
import javax.persistence.TemporalType;

import org.apache.commons.lang.StringUtils;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.identifier.DBIdentifier;
import org.apache.openjpa.jdbc.identifier.QualifiedDBIdentifier;
import org.apache.openjpa.jdbc.kernel.EagerFetchModes;
import org.apache.openjpa.jdbc.meta.ClassMapping;
import org.apache.openjpa.jdbc.meta.ClassMappingInfo;
import org.apache.openjpa.jdbc.meta.DiscriminatorMappingInfo;
import org.apache.openjpa.jdbc.meta.FieldMapping;
import org.apache.openjpa.jdbc.meta.FieldMappingInfo;
import org.apache.openjpa.jdbc.meta.MappingInfo;
import org.apache.openjpa.jdbc.meta.MappingRepository;
import org.apache.openjpa.jdbc.meta.QueryResultMapping;
import org.apache.openjpa.jdbc.meta.SequenceMapping;
import org.apache.openjpa.jdbc.meta.QueryResultMapping.PCResult;
import org.apache.openjpa.jdbc.meta.strats.EnumValueHandler;
import org.apache.openjpa.jdbc.meta.strats.FlatClassStrategy;
import org.apache.openjpa.jdbc.meta.strats.FullClassStrategy;
import org.apache.openjpa.jdbc.meta.strats.NoneClassStrategy;
import org.apache.openjpa.jdbc.meta.strats.VerticalClassStrategy;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.Unique;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.meta.SourceTracker;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.persistence.XMLPersistenceMetaDataParser;
import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.MetaDataException;
import org.apache.openjpa.util.UserException;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;

import static org.apache.openjpa.persistence.jdbc.MappingTag.*;

/**
 * Custom SAX parser used by the system to parse persistence mapping files.
 *
 * @author Steve Kim
 * @nojavadoc
 */
public class XMLPersistenceMappingParser extends XMLPersistenceMetaDataParser {

    private static final Map<String, MappingTag> _elems = new HashMap<String, MappingTag>();

    static {
        _elems.put("association-override", ASSOC_OVERRIDE);
        _elems.put("attribute-override", ATTR_OVERRIDE);
        _elems.put("collection-table", COLLECTION_TABLE);
        _elems.put("column", COL);
        _elems.put("columns", COLS);
        _elems.put("column-name", COLUMN_NAME);
        _elems.put("column-result", COLUMN_RESULT);
        _elems.put("data-store-id-column", DATASTORE_ID_COL);
        _elems.put("delimited-identifiers", DELIMITED_IDS);
        _elems.put("discriminator-column", DISCRIM_COL);
        _elems.put("discriminator-value", DISCRIM_VAL);
        _elems.put("entity-result", ENTITY_RESULT);
        _elems.put("enumerated", ENUMERATED);
        _elems.put("field-result", FIELD_RESULT);
        _elems.put("foreign-key", FK);
        _elems.put("fk_column-names", FK_COL_NAMES);
        _elems.put("fk_column_name", FK_COL_NAME);
        _elems.put("inheritance", INHERITANCE);
        _elems.put("index", INDEX);
        _elems.put("join-column", JOIN_COL);
        _elems.put("inverse-join-column", COL);
        _elems.put("join-table", JOIN_TABLE);
        _elems.put("map-key-enumerated", MAP_KEY_ENUMERATED);
        _elems.put("map-key-column", MAP_KEY_COL);
        _elems.put("map-key-join-column", MAP_KEY_JOIN_COL);
        _elems.put("map-key-temporal", MAP_KEY_TEMPORAL);
        _elems.put("name", NAME);
        _elems.put("order-column", ORDER_COLUMN);
        _elems.put("primary-key-join-column", PK_JOIN_COL);
        _elems.put("secondary-table", SECONDARY_TABLE);
        _elems.put("sql-result-set-mapping", SQL_RESULT_SET_MAPPING);
        _elems.put("table", TABLE);
        _elems.put("table-generator", TABLE_GEN);
        _elems.put("temporal", TEMPORAL);
        _elems.put("unique-constraint", UNIQUE);
        _elems.put("version-columns", VERSION_COLS);
        _elems.put("version-column", VERSION_COL);
    }

    private static final Localizer _loc = Localizer.forPackage(XMLPersistenceMappingParser.class);

    private String _override = null;
    private String _schema = null;
    private String _colTable = null;
    private String _secondaryTable = null;
    private List<Column> _cols = null;
    private List<Column> _joinCols = null;
    private List<Column> _supJoinCols = null;
    private boolean _lob = false;
    private TemporalType _temporal = null;
    private EnumSet<UniqueFlag> _unique = EnumSet.noneOf(UniqueFlag.class);
    private DiscriminatorType _discType;
    private Column _discCol;
    private int _resultIdx = 0;
    private final DBDictionary _dict;

    // ForeignKey info
    private Attributes _foreignKeyAttributes = null;
    private List<String> _columnNamesList = null;
    private String[] _columnNames = {};

    private List<Column> _versionColumnsList = null;

    private final Map<Class<?>, ArrayList<DeferredEmbeddableOverrides>> _deferredMappings = new HashMap<Class<?>, ArrayList<DeferredEmbeddableOverrides>>();

    /**
     * Constructor; supply configuration.
     */
    public XMLPersistenceMappingParser(JDBCConfiguration conf) {
        super(conf);
        _dict = conf.getDBDictionaryInstance();
    }

    @Override
    protected void reset() {
        super.reset();
        clearColumnInfo();
        clearClassInfo();
        clearSecondaryTableInfo();
        _override = null;
        _schema = null;
        _resultIdx = 0;
    }

    @Override
    protected Object startSystemMappingElement(String name, Attributes attrs) throws SAXException {
        MappingTag tag = _elems.get(name);
        if (tag == null) {
            if ("schema".equals(name))
                return name;
            return null;
        }

        boolean ret;
        switch (tag) {
        case TABLE_GEN:
            ret = startTableGenerator(attrs);
            break;
        case SQL_RESULT_SET_MAPPING:
            ret = startSQLResultSetMapping(attrs);
            break;
        case ENTITY_RESULT:
            ret = startEntityResult(attrs);
            break;
        case FIELD_RESULT:
            ret = startFieldResult(attrs);
            break;
        case COLUMN_RESULT:
            ret = startColumnResult(attrs);
            break;
        default:
            ret = false;
        }
        return (ret) ? tag : null;
    }

    @Override
    protected void endSystemMappingElement(String name) throws SAXException {
        MappingTag tag = _elems.get(name);
        if (tag == null) {
            if ("schema".equals(name))
                _schema = currentText();
            return;
        }

        switch (tag) {
        case SQL_RESULT_SET_MAPPING:
            endSQLResultSetMapping();
            break;
        case ENTITY_RESULT:
            endEntityResult();
            break;
        }
    }

    @Override
    protected Object startClassMappingElement(String name, Attributes attrs) throws SAXException {
        MappingTag tag = _elems.get(name);
        if (tag == null)
            return null;

        boolean ret;
        switch (tag) {
        case TABLE:
            ret = startTable(attrs);
            break;
        case SECONDARY_TABLE:
            ret = startSecondaryTable(attrs);
            break;
        case DISCRIM_COL:
            parseDiscriminatorColumn(attrs);
            _discCol = parseColumn(attrs);
            ret = true;
            break;
        case DISCRIM_VAL:
            ret = true;
            break;
        case INHERITANCE:
            ret = startInheritance(attrs);
            break;
        case ASSOC_OVERRIDE:
        case ATTR_OVERRIDE:
            ret = startAttributeOverride(attrs);
            break;
        case PK_JOIN_COL:
            ret = startPrimaryKeyJoinColumn(attrs);
            break;
        case COL:
            ret = startColumn(attrs);
            break;
        case COLS:
            ret = true;
            break;
        case JOIN_COL:
            ret = startJoinColumn(attrs);
            break;
        case JOIN_TABLE:
            ret = startJoinTable(attrs);
            break;
        case TABLE_GEN:
            ret = startTableGenerator(attrs);
            break;
        case UNIQUE:
            ret = startUniqueConstraint(attrs);
            break;
        case NAME:
            ret = true;
            break;
        case TEMPORAL:
        case ENUMERATED:
        case MAP_KEY_ENUMERATED:
        case MAP_KEY_TEMPORAL:
            ret = true;
            break;
        case SQL_RESULT_SET_MAPPING:
            ret = startSQLResultSetMapping(attrs);
            break;
        case ENTITY_RESULT:
            ret = startEntityResult(attrs);
            break;
        case FIELD_RESULT:
            ret = startFieldResult(attrs);
            break;
        case COLUMN_RESULT:
            ret = startColumnResult(attrs);
            break;
        case COLUMN_NAME:
            ret = true;
            break;
        case COLLECTION_TABLE:
            ret = startCollectionTable(attrs);
            break;
        case MAP_KEY_COL:
            ret = startMapKeyColumn(attrs);
            break;
        case MAP_KEY_JOIN_COL:
            ret = startMapKeyJoinColumn(attrs);
            break;
        case DATASTORE_ID_COL:
            ret = startDatastoreIdCol(attrs);
            break;
        case INDEX:
            ret = startIndex(attrs);
            break;
        case FK:
            ret = startForeignKey(attrs);
            break;
        case FK_COL_NAMES:
            ret = startFKColumnNames(attrs);
            break;
        case FK_COL_NAME:
            ret = true;
            break;
        case VERSION_COLS:
            ret = startVersionColumns(attrs);
            break;
        case VERSION_COL:
            ret = startVersionColumn(attrs);
            break;
        default:
            ret = false;
        }
        return (ret) ? tag : null;
    }

    private boolean endName() {
        String name = this.currentText();
        if (StringUtils.isNotEmpty(name)) {
            Object current = currentElement();
            if (current instanceof Unique) {
                Unique unq = (Unique) current;
                unq.setIdentifier(DBIdentifier.newConstraint(name, delimit()));
            }
        }

        return true;
    }

    @Override
    protected void endClassMappingElement(String name) throws SAXException {
        MappingTag tag = _elems.get(name);
        if (tag == null)
            return;

        switch (tag) {
        case SECONDARY_TABLE:
            endSecondaryTable();
            break;
        case DISCRIM_VAL:
            endDiscriminatorValue();
            break;
        case ASSOC_OVERRIDE:
        case ATTR_OVERRIDE:
            endAttributeOverride();
            break;
        case JOIN_TABLE:
            endJoinTable();
            break;
        case TEMPORAL:
            endTemporal();
            break;
        case MAP_KEY_TEMPORAL:
            endMapKeyTemporal();
            break;
        case ENUMERATED:
            endEnumerated();
            break;
        case MAP_KEY_ENUMERATED:
            endMapKeyEnumerated();
            break;
        case SQL_RESULT_SET_MAPPING:
            endSQLResultSetMapping();
            break;
        case ENTITY_RESULT:
            endEntityResult();
            break;
        case UNIQUE:
            endUniqueConstraint();
            break;
        case COLUMN_NAME:
            endColumnName();
            break;
        case TABLE_GEN:
            endTableGenerator();
            break;
        case NAME:
            endName();
            break;
        case FK:
            endForeignKey();
            break;
        case FK_COL_NAMES:
            endFKColumnNames();
            break;
        case FK_COL_NAME:
            endFKColumnName();
            break;
        case VERSION_COLS:
            endVersionColumns();
            break;
        }
    }

    @Override
    protected void startClassMapping(ClassMetaData meta, boolean mappedSuper, Attributes attrs)
            throws SAXException {
        if (mappedSuper)
            ((ClassMapping) meta).getMappingInfo().setStrategy(NoneClassStrategy.ALIAS);
    }

    @Override
    protected void endClassMapping(ClassMetaData meta) throws SAXException {
        ClassMapping cm = (ClassMapping) meta;
        if (_schema != null)
            cm.getMappingInfo().setSchemaIdentifier(DBIdentifier.newSchema(_schema, delimit()));

        if (_supJoinCols != null)
            cm.getMappingInfo().setColumns(_supJoinCols);

        if (_discCol != null) {
            DiscriminatorMappingInfo dinfo = cm.getDiscriminator().getMappingInfo();
            switch (_discType) {
            case CHAR:
                _discCol.setJavaType(JavaTypes.CHAR);
                cm.getDiscriminator().setJavaType(JavaTypes.CHAR);
                break;
            case INTEGER:
                _discCol.setJavaType(JavaTypes.INT);
                cm.getDiscriminator().setJavaType(JavaTypes.INT);
                break;
            default:
                _discCol.setJavaType(JavaTypes.STRING);
                cm.getDiscriminator().setJavaType(JavaTypes.STRING);
                break;
            }
            dinfo.setColumns(Arrays.asList(new Column[] { _discCol }));
        }
        clearClassInfo();
    }

    /**
     * Clear cached class mapping info.
     */
    private void clearClassInfo() {
        _supJoinCols = null;
        _discCol = null;
        _discType = null;
    }

    /**
     * Start tracking secondary table information and columns
     */
    private boolean startSecondaryTable(Attributes attrs) throws SAXException {
        _secondaryTable = toTableIdentifier(attrs.getValue("schema"), attrs.getValue("name")).getName();
        ((ClassMapping) currentElement()).getMappingInfo()
                .addSecondaryTable(DBIdentifier.newTable(_secondaryTable));
        return true;
    }

    /**
     * Set the secondary table information back to the owning class mapping.
     */
    private void endSecondaryTable() {
        ClassMapping cm = (ClassMapping) currentElement();
        ClassMappingInfo info = cm.getMappingInfo();
        info.setSecondaryTableJoinColumns(DBIdentifier.newTable(_secondaryTable, delimit()), _joinCols);
        clearSecondaryTableInfo();
    }

    /**
     * Clear cached secondary table info.
     */
    private void clearSecondaryTableInfo() {
        _joinCols = null;
        _secondaryTable = null;
    }

    /**
     * Parse table-generator.
     */
    private boolean startTableGenerator(Attributes attrs) {
        String name = attrs.getValue("name");
        Log log = getLog();
        if (log.isTraceEnabled())
            log.trace(_loc.get("parse-gen", name));
        if (getRepository().getCachedSequenceMetaData(name) != null && log.isWarnEnabled())
            log.warn(_loc.get("override-gen", name));

        SequenceMapping seq = (SequenceMapping) getRepository().addSequenceMetaData(name);
        seq.setSequencePlugin(SequenceMapping.IMPL_VALUE_TABLE);
        seq.setTableIdentifier(toTableIdentifier(attrs.getValue("schema"), attrs.getValue("table")));
        seq.setPrimaryKeyColumnIdentifier(DBIdentifier.newColumn(attrs.getValue("pk-column-name"), delimit()));
        seq.setSequenceColumnIdentifier(DBIdentifier.newColumn(attrs.getValue("value-column-name"), delimit()));
        seq.setPrimaryKeyValue(attrs.getValue("pk-column-value"));
        String val = attrs.getValue("initial-value");
        if (val != null)
            seq.setInitialValue(Integer.parseInt(val));
        val = attrs.getValue("allocation-size");
        if (val != null)
            seq.setAllocate(Integer.parseInt(val));

        Object cur = currentElement();
        Object scope = (cur instanceof ClassMetaData) ? ((ClassMetaData) cur).getDescribedType() : null;
        seq.setSource(getSourceFile(), scope, SourceTracker.SRC_XML);
        Locator locator = getLocation().getLocator();
        if (locator != null) {
            seq.setLineNumber(locator.getLineNumber());
            seq.setColNumber(locator.getColumnNumber());
        }
        pushElement(seq);
        return true;
    }

    private void endTableGenerator() {
        popElement();
    }

    /**
     * Parse inheritance.
     */
    private boolean startInheritance(Attributes attrs) {
        String val = attrs.getValue("strategy");
        if (val == null)
            return true;

        ClassMapping cm = (ClassMapping) currentElement();
        ClassMappingInfo info = cm.getMappingInfo();
        switch (Enum.valueOf(InheritanceType.class, val)) {
        case SINGLE_TABLE:
            info.setHierarchyStrategy(FlatClassStrategy.ALIAS);
            break;
        case JOINED:
            info.setHierarchyStrategy(VerticalClassStrategy.ALIAS);
            break;
        case TABLE_PER_CLASS:
            info.setHierarchyStrategy(FullClassStrategy.ALIAS);
            break;
        }
        return true;
    }

    /**
     * Parse discriminator-value.
     */
    private void endDiscriminatorValue() {
        String val = currentText();
        if (StringUtils.isEmpty(val))
            return;

        ClassMapping cm = (ClassMapping) currentElement();
        cm.getDiscriminator().getMappingInfo().setValue(val);

        if (Modifier.isAbstract(cm.getDescribedType().getModifiers()) && getLog().isInfoEnabled()) {
            getLog().info(_loc.get("discriminator-on-abstract-class", cm.getDescribedType().getName()));
        }
    }

    /**
     * Parse temporal.
     */
    private void endTemporal() {
        String temp = currentText();
        if (!StringUtils.isEmpty(temp))
            _temporal = Enum.valueOf(TemporalType.class, temp);
    }

    /**
     * Parse temporal.
     */
    private void endMapKeyTemporal() {
        String temp = currentText();
        TemporalType _mapKeyTemporal = null;
        if (!StringUtils.isEmpty(temp))
            _mapKeyTemporal = Enum.valueOf(TemporalType.class, temp);
        FieldMapping fm = (FieldMapping) currentElement();
        List<Column> cols = fm.getKeyMapping().getValueInfo().getColumns();
        if (cols.isEmpty()) {
            cols = Arrays.asList(new Column[] { new Column() });
            fm.getKeyMapping().getValueInfo().setColumns(cols);
        }

        Column col = (Column) cols.get(0);
        switch (_mapKeyTemporal) {
        case DATE:
            col.setType(Types.DATE);
            break;
        case TIME:
            col.setType(Types.TIME);
            break;
        case TIMESTAMP:
            col.setType(Types.TIMESTAMP);
            break;
        }
    }

    /**
     * Parse enumerated.
     */
    private void endEnumerated() {
        String text = currentText();
        if (StringUtils.isEmpty(text))
            return;
        EnumType type = Enum.valueOf(EnumType.class, text);

        FieldMapping fm = (FieldMapping) currentElement();
        String strat = EnumValueHandler.class.getName() + "(StoreOrdinal="
                + String.valueOf(type == EnumType.ORDINAL) + ")";
        if (fm.isElementCollection())
            fm.getElementMapping().getValueInfo().setStrategy(strat);
        else
            fm.getValueInfo().setStrategy(strat);
    }

    /**
     * Parse map-key-enumerated.
     */
    private void endMapKeyEnumerated() {
        String text = currentText();
        if (StringUtils.isEmpty(text))
            return;
        EnumType type = Enum.valueOf(EnumType.class, text);

        FieldMapping fm = (FieldMapping) currentElement();
        String strat = EnumValueHandler.class.getName() + "(StoreOrdinal="
                + String.valueOf(type == EnumType.ORDINAL) + ")";
        fm.getKeyMapping().getValueInfo().setStrategy(strat);
    }

    @Override
    protected boolean startLob(Attributes attrs) throws SAXException {
        if (super.startLob(attrs)) {
            _lob = true;
            return true;
        }
        return false;
    }

    /**
     * Extend to clear annotation mapping info.
     */
    @Override
    protected void startFieldMapping(FieldMetaData field, Attributes attrs) throws SAXException {
        super.startFieldMapping(field, attrs);
        if (getAnnotationParser() != null) {
            FieldMapping fm = (FieldMapping) field;
            fm.getMappingInfo().clear();
            fm.getValueInfo().clear();
            fm.getElementMapping().getValueInfo().clear();
            fm.getKeyMapping().getValueInfo().clear();
        }
    }

    /**
     * Extend to set the columns.
     */
    @Override
    protected void endFieldMapping(FieldMetaData field) throws SAXException {
        // setup columns with cached lob and temporal info
        FieldMapping fm = (FieldMapping) field;
        if (_lob || _temporal != null) {
            int typeCode = fm.isElementCollection() ? fm.getElement().getDeclaredTypeCode()
                    : fm.getDeclaredTypeCode();
            Class<?> type = fm.isElementCollection() ? fm.getElement().getDeclaredType() : fm.getDeclaredType();
            if (_cols == null) {
                _cols = new ArrayList<Column>(1);
                _cols.add(new Column());
            }
            for (Column col : _cols) {
                if (_lob && (typeCode == JavaTypes.STRING || type == char[].class || type == Character[].class)) {
                    col.setSize(-1);
                    col.setType(Types.CLOB);
                } else if (_lob)
                    col.setType(Types.BLOB);
                else {
                    switch (_temporal) {
                    case DATE:
                        col.setType(Types.DATE);
                        break;
                    case TIME:
                        col.setType(Types.TIME);
                        break;
                    case TIMESTAMP:
                        col.setType(Types.TIMESTAMP);
                        break;
                    }
                }
            }
        }

        if (_cols != null) {
            switch (fm.getDeclaredTypeCode()) {
            case JavaTypes.ARRAY:
                Class<?> type = fm.getDeclaredType();
                if (type == byte[].class || type == Byte[].class || type == char[].class
                        || type == Character[].class) {
                    fm.getValueInfo().setColumns(_cols);
                    break;
                }
                // else no break
            case JavaTypes.COLLECTION:
                if (!fm.getValue().isSerialized()) {
                    fm.getElementMapping().getValueInfo().setColumns(_cols);
                } else {
                    fm.getValueInfo().setColumns(_cols);
                }
                break;
            case JavaTypes.MAP:
                fm.getElementMapping().getValueInfo().setColumns(_cols);
                break;
            default:
                fm.getValueInfo().setColumns(_cols);
            }
            if (_colTable != null)
                fm.getMappingInfo().setTableIdentifier(DBIdentifier.newTable(_colTable, delimit()));
            setUnique(fm);
        }
        clearColumnInfo();
    }

    /**
     * Set unique for field.
     */
    private void setUnique(FieldMapping fm) {
        setUnique(fm, _unique);
    }

    private void setUnique(FieldMapping fm, EnumSet<UniqueFlag> unique) {
        if (unique.size() == 2) // i.e. TRUE & FALSE
            getLog().warn(_loc.get("inconsist-col-attrs", fm));
        else if (unique.contains(UniqueFlag.TRUE))
            fm.getValueInfo().setUnique(new Unique());
    }

    /**
     * Clear field level column information.
     */
    private void clearColumnInfo() {
        _cols = null;
        _joinCols = null;
        _colTable = null;
        _lob = false;
        _temporal = null;
        _unique.clear();
    }

    /**
     * Parse attribute-override.
     */
    private boolean startAttributeOverride(Attributes attr) {
        _override = attr.getValue("name");
        return true;
    }

    /**
     * Set attribute override into proper mapping.
     */
    private void endAttributeOverride() throws SAXException {
        Object elem = currentElement();
        FieldMapping fm = null;
        if (elem instanceof ClassMapping)
            fm = getAttributeOverride((ClassMapping) elem);
        else {
            FieldMapping basefm = (FieldMapping) elem;

            fm = getAttributeOverrideForEmbeddable(basefm, _override, false);
            if (fm == null) {
                DeferredEmbeddableOverrides dfm = getDeferredFieldMappingInfo(
                        AnnotationPersistenceMappingParser.getEmbeddedClassType(basefm, _override), basefm,
                        _override, true);
                dfm._defCols = _cols;
                dfm._defTable = DBIdentifier.newTable(_colTable, delimit());
                dfm._attrName = _override;
                dfm._unique = _unique;
            }
        }
        if (fm != null && _cols != null) {
            fm.getValueInfo().setColumns(_cols);
            if (_colTable != null)
                fm.getMappingInfo().setTableIdentifier(DBIdentifier.newTable(_colTable, delimit()));
            setUnique(fm);
        }
        clearColumnInfo();
        _override = null;
    }

    /**
     * Return the proper override.
     */
    private FieldMapping getAttributeOverride(ClassMapping cm) {
        FieldMapping sup = (FieldMapping) cm.getDefinedSuperclassField(_override);
        if (sup == null)
            sup = (FieldMapping) cm.addDefinedSuperclassField(_override, Object.class, Object.class);
        return sup;
    }

    /**
     * Return the proper override.
     */
    private FieldMapping getAttributeOverrideForEmbeddable(FieldMapping fm, String attrName, boolean mustExist)
            throws SAXException {
        return AnnotationPersistenceMappingParser.getEmbeddedFieldMapping(fm, attrName, mustExist);
    }

    /**
     * Parse table.
     */
    private boolean startTable(Attributes attrs) throws SAXException {
        ClassMapping mapping = (ClassMapping) currentElement();
        if (mapping.isAbstract())
            throw new UserException(_loc.get("table-not-allowed", mapping));
        DBIdentifier table = toTableIdentifier(attrs.getValue("schema"), attrs.getValue("name"));
        if (!DBIdentifier.isNull(table))
            mapping.getMappingInfo().setTableIdentifier(table);
        return true;
    }

    /**
     * Parse join-table.
     */
    private boolean startJoinTable(Attributes attrs) throws SAXException {
        DBIdentifier sTable = toTableIdentifier(attrs.getValue("schema"), attrs.getValue("name"));
        if (!DBIdentifier.isNull(sTable)) {
            Object elem = currentElement();
            FieldMapping fm = null;
            if (elem instanceof FieldMapping) {
                fm = (FieldMapping) elem;
                if (_override != null) {
                    FieldMapping basefm = (FieldMapping) elem;
                    fm = getAttributeOverrideForEmbeddable(basefm, _override, false);
                    if (fm == null) {
                        DeferredEmbeddableOverrides dfm = getDeferredFieldMappingInfo(
                                AnnotationPersistenceMappingParser.getEmbeddedClassType(basefm, _override), basefm,
                                _override, true);
                        dfm._defTable = sTable.clone();
                        dfm._attrName = _override;
                    }
                }
            } else if (elem instanceof ClassMapping) {
                ClassMapping cm = (ClassMapping) elem;
                fm = getAttributeOverride(cm);
            }
            if (fm != null)
                fm.getMappingInfo().setTableIdentifier(sTable);
        }
        return true;
    }

    /**
     * Set the join table information back.
     */
    private void endJoinTable() throws SAXException {
        Object elem = currentElement();
        FieldMapping fm = null;
        if (elem instanceof FieldMapping) {
            fm = (FieldMapping) elem;
            if (_override != null) {
                FieldMapping basefm = (FieldMapping) elem;
                fm = getAttributeOverrideForEmbeddable(basefm, _override, false);
                if (fm == null) {
                    DeferredEmbeddableOverrides dfm = getDeferredFieldMappingInfo(
                            AnnotationPersistenceMappingParser.getEmbeddedClassType(basefm, _override), basefm,
                            _override, true);
                    dfm._defCols = _cols;
                    dfm._defElemJoinCols = _joinCols;
                    dfm._attrName = _override;
                }
            }
        } else if (elem instanceof ClassMapping) {
            ClassMapping cm = (ClassMapping) elem;
            fm = getAttributeOverride(cm);
        }

        if (fm != null) {
            if (_joinCols != null)
                fm.getMappingInfo().setColumns(_joinCols);
            if (_cols != null)
                fm.getElementMapping().getValueInfo().setColumns(_cols);
        }
        clearColumnInfo();
    }

    /**
     * Parse primary-key-join-column.
     */
    private boolean startPrimaryKeyJoinColumn(Attributes attrs) throws SAXException {
        Column col = parseColumn(attrs);
        col.setFlag(Column.FLAG_PK_JOIN, true);
        // pk join columns on fields act as field cols
        if (currentElement() instanceof FieldMapping) {
            if (_cols == null)
                _cols = new ArrayList<Column>(3);
            _cols.add(col);
        } else if (currentParent() == SECONDARY_TABLE) {
            // pk join columns in secondary table acts as join cols
            if (_joinCols == null)
                _joinCols = new ArrayList<Column>(3);
            _joinCols.add(col);
        } else {
            // must be pk join cols from this class to superclass
            if (_supJoinCols == null)
                _supJoinCols = new ArrayList<Column>(3);
            _supJoinCols.add(col);
        }
        return true;
    }

    /**
     * Parse join-column.
     */
    private boolean startJoinColumn(Attributes attrs) throws SAXException {
        // only join cols in a join table join field table to class table;
        // others act as data fk cols
        Object currentParent = currentParent();
        if (currentParent == COLLECTION_TABLE) {
            FieldMapping fm = (FieldMapping) peekElement();
            Column col = parseColumn(attrs);
            List<Column> colList = fm.getMappingInfo().getColumns();
            if (colList.isEmpty()) {
                colList = new ArrayList<Column>();
                fm.getMappingInfo().setColumns(colList);
            }
            colList.add(col);
            fm.getMappingInfo().setColumns(colList);
            return true;
        }

        if (currentParent != JOIN_TABLE)
            return startColumn(attrs);

        if (_joinCols == null)
            _joinCols = new ArrayList<Column>(3);
        _joinCols.add(parseColumn(attrs));
        return true;
    }

    /**
     * Parse column.
     */
    private boolean startColumn(Attributes attrs) throws SAXException {
        Column col = parseColumn(attrs);
        Object obj = peekElement();
        if (obj instanceof FieldMapping) {
            FieldMapping fm = (FieldMapping) obj;
            // a collection of basic types
            // the column is in a separate table
            if (fm.isElementCollection() && !fm.getElementMapping().isEmbedded()) {
                List<Column> list = fm.getElementMapping().getValueInfo().getColumns();
                if (list.size() == 0) {
                    list = new ArrayList<Column>();
                    fm.getElementMapping().getValueInfo().setColumns(list);
                }
                list.add(col);
                return true;
            }
        }
        if (_cols == null)
            _cols = new ArrayList<Column>(3);
        _cols.add(col);
        return true;
    }

    /**
     * Parse map-key-column.
     */
    private boolean startMapKeyColumn(Attributes attrs) throws SAXException {
        FieldMapping fm = (FieldMapping) peekElement();
        Column col = parseColumn(attrs);
        MappingInfo info = fm.getKeyMapping().getValueInfo();
        List<Column> cols = new ArrayList<Column>();
        cols.add(col);
        info.setColumns(cols);
        return true;
    }

    /**
     * Parse map-key-join-column.
     */
    private boolean startMapKeyJoinColumn(Attributes attrs) throws SAXException {
        boolean retVal = startMapKeyColumn(attrs);
        // check if name is not set, set it to default: the
        // concatenation of the name of the referencing property
        // or field name, "-", "KEY"
        FieldMapping fm = (FieldMapping) peekElement();
        MappingInfo info = fm.getKeyMapping().getValueInfo();
        List<Column> cols = info.getColumns();
        Column col = cols.get(0);
        if (DBIdentifier.isNull(col.getIdentifier())) {
            col.setIdentifier(DBIdentifier.newColumn(fm.getName() + "_" + "KEY", delimit()));
        }

        return retVal;
    }

    /**
     * Create a column with the given attributes.
     */
    private Column parseColumn(Attributes attrs) throws SAXException {
        Column col = new Column();
        String val = attrs.getValue("name");
        if (val != null)
            col.setIdentifier(DBIdentifier.newColumn(val, delimit()));
        val = attrs.getValue("referenced-column-name");
        if (val != null) {
            setTargetIdentifier(col, val);
        }
        val = attrs.getValue("column-definition");
        if (val != null)
            col.setTypeIdentifier(DBIdentifier.newColumnDefinition(val));
        val = attrs.getValue("precision");
        if (val != null)
            col.setSize(Integer.parseInt(val));
        val = attrs.getValue("length");
        if (val != null)
            col.setSize(Integer.parseInt(val));
        val = attrs.getValue("scale");
        if (val != null)
            col.setDecimalDigits(Integer.parseInt(val));
        val = attrs.getValue("nullable");
        if (val != null)
            col.setNotNull("false".equals(val));
        val = attrs.getValue("insertable");
        if (val != null)
            col.setFlag(Column.FLAG_UNINSERTABLE, "false".equals(val));
        val = attrs.getValue("updatable");
        if (val != null)
            col.setFlag(Column.FLAG_UNUPDATABLE, "false".equals(val));

        val = attrs.getValue("unique");
        if (val != null)
            _unique.add(Enum.valueOf(UniqueFlag.class, val.toUpperCase()));
        val = attrs.getValue("table");
        if (val != null) {
            if (_colTable != null && !_colTable.equals(val))
                throw getException(_loc.get("second-inconsist", currentElement()));
            _colTable = val;
        }
        return col;
    }

    /**
     * Sets reference column name of the given column taking into account
     * that the given reference name that begins with a single quote represents
     * special meaning of a constant join column and hence not to be delimited.  
     * @param col
     * @param refColumnName
     * @see <a href="http://issues.apache.org/jira/browse/OPENJPA-1979">OPENJPA-1979</a>
     */
    private static final char SINGLE_QUOTE = '\'';

    protected void setTargetIdentifier(Column col, String refColumnName) {
        if (refColumnName.charAt(0) == SINGLE_QUOTE) {
            col.setTargetIdentifier(DBIdentifier.newConstant(refColumnName));
        } else {
            col.setTargetIdentifier(DBIdentifier.newColumn(refColumnName, delimit()));
        }
    }

    /**
     * Parse collectionTable.
     */
    private boolean startCollectionTable(Attributes attrs) throws SAXException {
        FieldMapping fm = (FieldMapping) peekElement();

        FieldMappingInfo info = fm.getMappingInfo();
        DBIdentifier ctbl = parseCollectionTable(attrs);
        info.setTableIdentifier(ctbl);
        return true;
    }

    private DBIdentifier parseCollectionTable(Attributes attrs) {
        String tVal = attrs.getValue("name");
        String sVal = attrs.getValue("schema");
        return toTableIdentifier(sVal, tVal);
    }

    /**
     * Form a qualified table name from a schema and table name.
     */
    private DBIdentifier toTableIdentifier(String schema, String table) {
        DBIdentifier sName = DBIdentifier.newSchema(schema, delimit());
        DBIdentifier tName = DBIdentifier.newTable(table, delimit());
        if (DBIdentifier.isEmpty(tName) || DBIdentifier.isEmpty(sName)) {
            return tName;
        }
        return QualifiedDBIdentifier.newPath(sName, tName);
    }

    /**
     * Start processing <code>sql-result-set-mapping</code> node.
     * Pushes the {@link QueryResultMapping} onto the stack as current element.
     */
    private boolean startSQLResultSetMapping(Attributes attrs) {
        String name = attrs.getValue("name");
        Log log = getLog();
        if (log.isTraceEnabled())
            log.trace(_loc.get("parse-sqlrsmapping", name));

        MappingRepository repos = (MappingRepository) getRepository();
        QueryResultMapping result = new QueryResultMapping(name, repos);
        if (repos.addQueryResultMapping(result) != null) {
            log.warn(_loc.get("override-sqlrsmapping", name, currentLocation()));
        }
        result.setListingIndex(_resultIdx++);
        addComments(result);

        Object cur = currentElement();
        Object scope = (cur instanceof ClassMetaData) ? ((ClassMetaData) cur).getDescribedType() : null;
        result.setSource(getSourceFile(), scope, SourceTracker.SRC_XML);
        Locator locator = getLocation().getLocator();
        if (locator != null) {
            result.setLineNumber(locator.getLineNumber());
            result.setColNumber(locator.getColumnNumber());
        }
        pushElement(result);
        return true;
    }

    private void endSQLResultSetMapping() throws SAXException {
        popElement();
    }

    /**
     * Start processing <code>entity-result</code> node.
     * Pushes the {@link QueryResultMapping.PCResult}
     * onto the stack as current element.
     */
    private boolean startEntityResult(Attributes attrs) throws SAXException {
        Class<?> entityClass = classForName(attrs.getValue("entity-class"));
        String discriminator = DBIdentifier.newColumn(attrs.getValue("discriminator-column"), delimit()).getName();

        QueryResultMapping parent = (QueryResultMapping) currentElement();
        QueryResultMapping.PCResult result = parent.addPCResult(entityClass);
        if (!StringUtils.isEmpty(discriminator))
            result.addMapping(PCResult.DISCRIMINATOR, discriminator);
        pushElement(result);
        return true;
    }

    private void endEntityResult() throws SAXException {
        popElement();
    }

    /**
     * Process a <code>field-result</code> node.
     */
    private boolean startFieldResult(Attributes attrs) throws SAXException {
        String fieldName = attrs.getValue("name");
        String columnName = DBIdentifier.newColumn(attrs.getValue("column"), delimit()).getName();

        QueryResultMapping.PCResult parent = (QueryResultMapping.PCResult) currentElement();
        parent.addMapping(fieldName, columnName);
        return true;
    }

    /**
     * Process a <code>column-result</code> node.
     */
    private boolean startColumnResult(Attributes attrs) throws SAXException {
        QueryResultMapping parent = (QueryResultMapping) currentElement();
        parent.addColumnResult(attrs.getValue("name"));
        return true;
    }

    /** 
     * Starts processing &lt;unique-constraint&gt; provided the tag occurs
     * within a ClassMapping element and <em>not</em> within a secondary
     * table. 
     * Pushes the Unique element in the stack.
     */
    private boolean startUniqueConstraint(Attributes attrs) throws SAXException {
        Unique unique = new Unique();

        DBIdentifier name = DBIdentifier.newConstraint(attrs.getValue("name"), delimit());
        if (!DBIdentifier.isEmpty(name)) {
            unique.setIdentifier(name);
        }

        pushElement(unique);
        return true;
    }

    /**
     * Ends processing &lt;unique-constraint&gt; provided the tag occurs
     * within a ClassMapping element and <em>not</em> within a secondary
     * table. The stack is popped and the Unique element is added to the
     * ClassMappingInfo. 
     */
    private void endUniqueConstraint() {
        Unique unique = (Unique) popElement();
        Object ctx = currentElement();
        DBIdentifier tableName = DBIdentifier.newTable("?");
        if (ctx instanceof ClassMapping) {
            ClassMappingInfo info = ((ClassMapping) ctx).getMappingInfo();
            tableName = (_secondaryTable == null) ? info.getTableIdentifier()
                    : DBIdentifier.newTable(_secondaryTable, delimit());
            info.addUnique(tableName, unique);
        } else if (ctx instanceof FieldMapping) {// JoinTable
            FieldMappingInfo info = ((FieldMapping) ctx).getMappingInfo();
            info.addJoinTableUnique(unique);
        } else if (ctx instanceof SequenceMapping) {
            SequenceMapping seq = (SequenceMapping) ctx;
            unique.setTableIdentifier(seq.getTableIdentifier());
            Column[] uniqueColumns = unique.getColumns();
            DBIdentifier[] columnNames = new DBIdentifier[uniqueColumns.length];
            int i = 0;
            for (Column uniqueColumn : uniqueColumns)
                columnNames[i++] = uniqueColumn.getIdentifier().clone();
            seq.setUniqueColumnsIdentifier(columnNames);
            if (!DBIdentifier.isEmpty(unique.getIdentifier())) {
                seq.setUniqueConstraintIdentifier(unique.getIdentifier());
            }
        } else {
            throw new InternalException();
        }
    }

    /**
     * Ends processing &lt;column-name&gt; tag by adding the column name in
     * the current Unique element that resides in the top of the stack.
     */
    private boolean endColumnName() {
        Object current = currentElement();
        if (current instanceof Unique) {
            Unique unique = (Unique) current;
            Column column = new Column();
            column.setIdentifier(DBIdentifier.newColumn(this.currentText(), delimit()));
            unique.addColumn(column);
            return true;
        }
        return false;
    }

    /**
     * Track unique column settings.
    */
    private static enum UniqueFlag {
        TRUE, FALSE
    }

    private void parseDiscriminatorColumn(Attributes attrs) {
        String val = attrs.getValue("discriminator-type");
        if (val != null) {
            _discType = Enum.valueOf(DiscriminatorType.class, val);
        } else {
            _discType = DiscriminatorType.STRING;
        }

    }

    /**
     * Process OrderColumn.
     */
    protected boolean startOrderColumn(Attributes attrs) throws SAXException {
        Column col = parseOrderColumn(attrs);
        Object obj = peekElement();
        if (obj instanceof FieldMapping) {
            FieldMapping fm = (FieldMapping) obj;
            fm.getMappingInfo().setOrderColumn(col);

        }
        return true;
    }

    /**
     * Create an order column with the given attributes.
     */
    private Column parseOrderColumn(Attributes attrs) throws SAXException {

        Column col = new Column();
        String val = attrs.getValue("name");
        if (val != null)
            col.setIdentifier(DBIdentifier.newColumn(val, delimit()));
        val = attrs.getValue("column-definition");
        if (val != null)
            col.setTypeIdentifier(DBIdentifier.newColumnDefinition(val));
        val = attrs.getValue("precision");
        if (val != null)
            col.setSize(Integer.parseInt(val));
        val = attrs.getValue("length");
        if (val != null)
            col.setSize(Integer.parseInt(val));
        val = attrs.getValue("scale");
        if (val != null)
            col.setDecimalDigits(Integer.parseInt(val));
        val = attrs.getValue("nullable");
        if (val != null)
            col.setNotNull("false".equals(val));
        val = attrs.getValue("insertable");
        if (val != null)
            col.setFlag(Column.FLAG_UNINSERTABLE, "false".equals(val));
        val = attrs.getValue("updatable");
        if (val != null)
            col.setFlag(Column.FLAG_UNUPDATABLE, "false".equals(val));

        return col;
    }

    /**
     * Process all deferred embeddable overrides for a given class.  
     * This should only occur after the embeddable is mapped.
     * 
     * @param embedType  embeddable class 
     * @param access class level access for embeddable
     * @throws SAXException 
     */
    @Override
    protected void applyDeferredEmbeddableOverrides(Class<?> cls) throws SAXException {
        ArrayList<DeferredEmbeddableOverrides> defMappings = _deferredMappings.get(cls);
        if (defMappings == null)
            return;

        for (DeferredEmbeddableOverrides defMap : defMappings) {
            FieldMapping fm = (FieldMapping) defMap._fm;
            if (defMap == null)
                return;
            fm = getAttributeOverrideForEmbeddable(fm, defMap._attrName, true);
            // Apply column, table, and unique overrides
            if (defMap._defCols != null) {
                fm.getValueInfo().setColumns(defMap._defCols);
                if (!DBIdentifier.isNull(defMap._defTable))
                    fm.getMappingInfo().setTableIdentifier(defMap._defTable);
                setUnique(fm, defMap._unique);
            }
            // Apply Join column and element join columns overrides overrides
            if (defMap._defJoinCols != null)
                fm.getMappingInfo().setColumns(defMap._defJoinCols);
            if (defMap._defElemJoinCols != null)
                fm.getElementMapping().getValueInfo().setColumns(defMap._defElemJoinCols);
        }
        // Clean up after applying mappings
        defMappings.clear();
        _deferredMappings.remove(cls);
    }

    /*
     * Defer overrides for the specified field mapping
     */
    private void deferEmbeddableOverrides(Class<?> cls, DeferredEmbeddableOverrides defMap) {
        ArrayList<DeferredEmbeddableOverrides> defMappings = _deferredMappings.get(cls);
        if (defMappings == null) {
            defMappings = new ArrayList<DeferredEmbeddableOverrides>();
            _deferredMappings.put(cls, defMappings);
        }
        defMappings.add(defMap);
    }

    /*
     * Clean up any deferred mappings
     */
    @Override
    protected void clearDeferredMetaData() {
        super.clearDeferredMetaData();
        _deferredMappings.clear();
    }

    /*
     * Get embeddable overrides for the specified field mapping.  If create
     * is true, create a new override if one does not exist.
     */
    private DeferredEmbeddableOverrides getDeferredFieldMappingInfo(Class<?> cls, FieldMapping fm, String attrName,
            boolean create) {

        ArrayList<DeferredEmbeddableOverrides> defMappings = _deferredMappings.get(cls);

        if (defMappings == null && create) {
            defMappings = new ArrayList<DeferredEmbeddableOverrides>();
            _deferredMappings.put(cls, defMappings);
        }
        DeferredEmbeddableOverrides dfm = findDeferredMapping(cls, fm, attrName);

        if (dfm == null & create) {
            dfm = new DeferredEmbeddableOverrides(fm, attrName);
            deferEmbeddableOverrides(cls, dfm);
        }
        return dfm;
    }

    /*
     * Find deferred mappings for the given class, fm, and attr name
     */
    private DeferredEmbeddableOverrides findDeferredMapping(Class<?> cls, FieldMapping fm, String attrName) {
        ArrayList<DeferredEmbeddableOverrides> defMappings = _deferredMappings.get(cls);
        if (defMappings == null)
            return null;

        for (DeferredEmbeddableOverrides dfm : defMappings) {
            if (dfm != null && dfm._fm == fm && attrName.equals(dfm._attrName))
                return dfm;
        }
        return null;
    }

    /**
     * Process all deferred embeddables using an unknown access type.
     */
    protected void addDeferredEmbeddableMetaData() {
        super.addDeferredEmbeddableMetaData();
        if (_deferredMappings.size() > 0) {
            Set<Class<?>> keys = _deferredMappings.keySet();
            Class<?>[] classes = keys.toArray(new Class[keys.size()]);
            for (int i = 0; i < classes.length; i++) {
                try {
                    applyDeferredEmbeddableOverrides(classes[i]);
                } catch (Exception e) {
                    throw new MetaDataException(_loc.get("no-embeddable-metadata", classes[i].getName()), e);
                }
            }
        }

    }

    // Inner class for storing override information
    class DeferredEmbeddableOverrides {
        DeferredEmbeddableOverrides(FieldMapping fm, String attrName) {
            _fm = fm;
            _attrName = attrName;
            _defTable = DBIdentifier.NULL;
        }

        private FieldMapping _fm;
        private List<Column> _defCols;
        private List<Column> _defElemJoinCols;
        private List<Column> _defJoinCols;
        private DBIdentifier _defTable;
        private String _attrName;
        private EnumSet<UniqueFlag> _unique;
    }

    @Override
    protected boolean startDelimitedIdentifiers() {
        JDBCConfiguration conf = (JDBCConfiguration) getConfiguration();
        DBDictionary dict = conf.getDBDictionaryInstance();
        dict.setDelimitIdentifiers(true);
        return true;
    }

    @Override
    protected String normalizeSequenceName(String seqName) {
        if (StringUtils.isEmpty(seqName)) {
            return seqName;
        }
        return DBIdentifier.newSequence(seqName, delimit()).getName();
    }

    @Override
    protected String normalizeSchemaName(String schName) {
        if (StringUtils.isEmpty(schName)) {
            return schName;
        }
        return DBIdentifier.newSchema(schName, delimit()).getName();
    }

    @Override
    protected String normalizeCatalogName(String catName) {
        if (StringUtils.isEmpty(catName)) {
            return catName;
        }
        return DBIdentifier.newCatalog(catName, delimit()).getName();
    }

    private boolean delimit() {
        return _dict.getDelimitIdentifiers();
    }

    /**
     * Translate the fetch mode enum value to the internal OpenJPA constant.
     */
    private static int toEagerFetchModeConstant(String mode) {
        if (mode.equals("NONE"))
            return EagerFetchModes.EAGER_NONE;
        else if (mode.equals("JOIN"))
            return EagerFetchModes.EAGER_JOIN;
        else if (mode.equals("PARALLEL"))
            return EagerFetchModes.EAGER_PARALLEL;
        else
            throw new InternalException();
    }

    private boolean startDatastoreIdCol(Attributes attrs) throws SAXException {

        ClassMapping cm = (ClassMapping) peekElement();

        Column col = new Column();
        String name = attrs.getValue("name");
        if (!StringUtils.isEmpty(name))
            ;
        col.setIdentifier(DBIdentifier.newColumn(name, delimit()));
        String columnDefinition = attrs.getValue("column-definition");
        if (!StringUtils.isEmpty(columnDefinition))
            col.setTypeIdentifier(DBIdentifier.newColumnDefinition(columnDefinition));
        int precision = Integer.parseInt(attrs.getValue("precision"));
        if (precision != 0)
            col.setSize(precision);
        col.setFlag(Column.FLAG_UNINSERTABLE, !Boolean.parseBoolean(attrs.getValue("insertable")));
        col.setFlag(Column.FLAG_UNUPDATABLE, !Boolean.parseBoolean(attrs.getValue("updatable")));
        cm.getMappingInfo().setColumns(Arrays.asList(new Column[] { col }));

        return true;
    }

    private boolean startIndex(Attributes attrs) throws SAXException {

        FieldMapping fm = (FieldMapping) peekElement();

        parseIndex(fm.getValueInfo(), attrs.getValue("name"), Boolean.parseBoolean(attrs.getValue("enabled")),
                Boolean.parseBoolean(attrs.getValue("unique")));

        return true;
    }

    private void parseIndex(MappingInfo info, String name, boolean enabled, boolean unique) {
        if (!enabled) {
            info.setCanIndex(false);
            return;
        }

        org.apache.openjpa.jdbc.schema.Index idx = new org.apache.openjpa.jdbc.schema.Index();
        if (!StringUtils.isEmpty(name))
            idx.setIdentifier(DBIdentifier.newConstraint(name, delimit()));
        idx.setUnique(unique);
        info.setIndex(idx);
    }

    private boolean startForeignKey(Attributes attrs) throws SAXException {

        _foreignKeyAttributes = attrs;

        return true;
    }

    private void endForeignKey() {
        if (_foreignKeyAttributes == null) {
            throw new InternalException();
        }

        boolean implicit = Boolean.parseBoolean(_foreignKeyAttributes.getValue("implicit"));

        FieldMapping fm = (FieldMapping) peekElement();
        MappingInfo info = fm.getValueInfo();

        String name = _foreignKeyAttributes.getValue("name");
        boolean enabled = Boolean.parseBoolean(_foreignKeyAttributes.getValue("enabled"));
        boolean deferred = Boolean.parseBoolean(_foreignKeyAttributes.getValue("deferred"));
        boolean specified = Boolean.parseBoolean(_foreignKeyAttributes.getValue("specified"));
        String deleteActionString = _foreignKeyAttributes.getValue("delete-action");
        String updateActionString = _foreignKeyAttributes.getValue("update-action");
        int deleteAction = toForeignKeyInt(deleteActionString);
        int updateAction = toForeignKeyInt(updateActionString);

        if (!implicit) {
            parseForeignKey(info, name, enabled, deferred, deleteAction, updateAction);
        } else {
            info.setImplicitRelation(true);
            assertDefault(name, enabled, deferred, specified, updateAction, deleteAction);
        }

        _columnNamesList = null;
    }

    private void parseForeignKey(MappingInfo info, String name, boolean enabled, boolean deferred, int deleteAction,
            int updateAction) {

        if (!enabled) {
            info.setCanForeignKey(false);
            return;
        }

        org.apache.openjpa.jdbc.schema.ForeignKey fk = new org.apache.openjpa.jdbc.schema.ForeignKey();
        if (!StringUtils.isEmpty(name))
            fk.setIdentifier(DBIdentifier.newForeignKey(name, delimit()));
        fk.setDeferred(deferred);
        fk.setDeleteAction(deleteAction);
        fk.setUpdateAction(updateAction);
        info.setForeignKey(fk);

    }

    private int toForeignKeyInt(String action) {
        if (action.equals("RESTRICT")) {
            return org.apache.openjpa.jdbc.schema.ForeignKey.ACTION_RESTRICT;
        } else if (action.equals("CASCADE")) {
            return org.apache.openjpa.jdbc.schema.ForeignKey.ACTION_CASCADE;
        } else if (action.equals("NULL")) {
            return org.apache.openjpa.jdbc.schema.ForeignKey.ACTION_NULL;
        } else if (action.equals("DEFAULT")) {
            return org.apache.openjpa.jdbc.schema.ForeignKey.ACTION_DEFAULT;
        } else {
            throw new InternalException();
        }

    }

    private void assertDefault(String name, boolean enabled, boolean deferred, boolean specified, int updateAction,
            int deleteAction) {
        boolean isDefault = StringUtils.isEmpty(name) && enabled && !deferred
                && deleteAction == org.apache.openjpa.jdbc.schema.ForeignKey.ACTION_RESTRICT
                && updateAction == org.apache.openjpa.jdbc.schema.ForeignKey.ACTION_RESTRICT
                && _columnNames.length == 0 && specified;
        if (!isDefault)
            throw new UserException(_loc.get("implicit-non-default-fk", _cls, getSourceFile()).getMessage());
    }

    private boolean startFKColumnNames(Attributes attrs) throws SAXException {
        _columnNamesList = new ArrayList<String>();
        return true;
    }

    private void endFKColumnNames() {
        if (_columnNamesList.size() > 0) {
            _columnNames = _columnNamesList.toArray(_columnNames);
            _columnNamesList.removeAll(_columnNamesList);
        }
    }

    private void endFKColumnName() {
        _columnNamesList.add(currentText());
    }

    private boolean startVersionColumns(Attributes attrs) throws SAXException {

        _versionColumnsList = new ArrayList<Column>();

        return true;
    }

    private void endVersionColumns() {
        if (_versionColumnsList == null) {
            throw new InternalException();
        }

        if (_versionColumnsList.size() > 0) {
            ClassMapping cm = (ClassMapping) peekElement();
            cm.getVersion().getMappingInfo().setColumns(_versionColumnsList);
            _versionColumnsList = null;
        }
    }

    private boolean startVersionColumn(Attributes attrs) throws SAXException {

        Column col = AnnotationPersistenceMappingParser.newColumn(attrs.getValue("name"),
                Boolean.parseBoolean(attrs.getValue("nullable")),
                Boolean.parseBoolean(attrs.getValue("insertable")),
                Boolean.parseBoolean(attrs.getValue("updatable")), attrs.getValue("columnDefinition"),
                Integer.parseInt(attrs.getValue("length")), Integer.parseInt(attrs.getValue("precision")),
                Integer.parseInt(attrs.getValue("scale")), attrs.getValue("table"), delimit());

        _versionColumnsList.add(col);

        return true;
    }

    @Override
    protected void parseEagerFetchModeAttr(FieldMetaData fmd, Attributes attrs) throws SAXException {

        FieldMapping fm = (FieldMapping) fmd;
        String eagerFetchMode = attrs.getValue("eager-fetch-mode");
        if (!StringUtils.isEmpty(eagerFetchMode)) {
            if (eagerFetchMode.equalsIgnoreCase("NONE")) {
                fm.setEagerFetchMode(EagerFetchModes.EAGER_NONE);
            } else if (eagerFetchMode.equalsIgnoreCase("JOIN")) {
                fm.setEagerFetchMode(EagerFetchModes.EAGER_JOIN);
            } else if (eagerFetchMode.equalsIgnoreCase("PARALLEL")) {
                fm.setEagerFetchMode(EagerFetchModes.EAGER_PARALLEL);
            }
        }
    }

    @Override
    protected void parseElementClassCriteriaAttr(FieldMetaData fmd, Attributes attrs) throws SAXException {

        String elementClassCriteriaString = attrs.getValue("element-class-criteria");
        if (!StringUtils.isEmpty(elementClassCriteriaString)) {
            FieldMapping fm = (FieldMapping) fmd;
            boolean elementClassCriteria = Boolean.parseBoolean(elementClassCriteriaString);
            fm.getElementMapping().getValueInfo().setUseClassCriteria(elementClassCriteria);
        }
    }

    @Override
    protected void parseStrategy(FieldMetaData fmd, Attributes attrs) {
        String strategy = attrs.getValue("strategy");
        if (!StringUtils.isEmpty(strategy)) {
            ((FieldMapping) fmd).getMappingInfo().setStrategy(strategy);
        }
    }

    @Override
    protected boolean startExtendedClass(String elem, Attributes attrs) throws SAXException {
        ClassMapping mapping = (ClassMapping) currentElement();

        String strategy = attrs.getValue("strategy");
        if (!StringUtils.isEmpty(strategy))
            mapping.getMappingInfo().setStrategy(strategy);

        String versionStrat = attrs.getValue("version-strategy");
        if (!StringUtils.isEmpty(versionStrat))
            mapping.getVersion().getMappingInfo().setStrategy(versionStrat);

        String discrimStrat = attrs.getValue("discriminator-strategy");
        if (!StringUtils.isEmpty(discrimStrat))
            mapping.getDiscriminator().getMappingInfo().setStrategy(discrimStrat);

        String subclassFetchMode = attrs.getValue("subclass-fetch-mode");
        if (!StringUtils.isEmpty(subclassFetchMode))
            mapping.setSubclassFetchMode(toEagerFetchModeConstant(subclassFetchMode));

        return true;
    }
}