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

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import javax.persistence.CascadeType;
import javax.persistence.GenerationType;
import javax.persistence.LockModeType;
import javax.persistence.NamedQuery;

import static javax.persistence.CascadeType.*;

import org.apache.commons.lang.StringUtils;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.enhance.PersistenceCapable;
import org.apache.openjpa.event.BeanLifecycleCallbacks;
import org.apache.openjpa.event.LifecycleCallbacks;
import org.apache.openjpa.event.LifecycleEvent;
import org.apache.openjpa.event.MethodLifecycleCallbacks;
import org.apache.openjpa.kernel.QueryLanguages;
import org.apache.openjpa.kernel.jpql.JPQLParser;
import org.apache.openjpa.lib.conf.Configurations;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.meta.CFMetaDataParser;
import org.apache.openjpa.lib.meta.SourceTracker;
import org.apache.openjpa.lib.meta.XMLVersionParser;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.meta.AccessCode;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.DelegatingMetaDataFactory;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.meta.LifecycleMetaData;
import org.apache.openjpa.meta.MetaDataContext;
import org.apache.openjpa.meta.MetaDataFactory;
import static org.apache.openjpa.meta.MetaDataModes.*;
import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.meta.Order;
import org.apache.openjpa.meta.QueryMetaData;
import org.apache.openjpa.meta.SequenceMetaData;
import org.apache.openjpa.meta.ValueMetaData;

import static org.apache.openjpa.persistence.MetaDataTag.*;
import static org.apache.openjpa.persistence.PersistenceStrategy.*;
import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.MetaDataException;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;


/**
 * Custom SAX parser used by the system to quickly parse persistence
 * metadata files. This parser may invoke
 * {@linkplain AnnotationPersistenceMetaDataParser another parser} to scan
 * source code annotation.
 *
 * @author Steve Kim
 * @author Pinaki Poddar
 * @nojavadoc
 */
public class XMLPersistenceMetaDataParser
    extends CFMetaDataParser
    implements PersistenceMetaDataFactory.Parser {

    // parse constants
    protected static final String ELEM_PKG = "package";
    protected static final String ELEM_ACCESS = "access";
    protected static final String ELEM_ATTRS = "attributes";
    protected static final String ELEM_LISTENER = "entity-listener";
    protected static final String ELEM_CASCADE = "cascade";
    protected static final String ELEM_CASCADE_ALL = "cascade-all";
    protected static final String ELEM_CASCADE_PER = "cascade-persist";
    protected static final String ELEM_CASCADE_MER = "cascade-merge";
    protected static final String ELEM_CASCADE_REM = "cascade-remove";
    protected static final String ELEM_CASCADE_REF = "cascade-refresh";
    protected static final String ELEM_CASCADE_DET = "cascade-detach";
    protected static final String ELEM_PU_META = "persistence-unit-metadata";
    protected static final String ELEM_PU_DEF = "persistence-unit-defaults";
    protected static final String ELEM_XML_MAP_META_COMPLETE = "xml-mapping-metadata-complete";
    protected static final String ELEM_DELIM_IDS = "delimited-identifiers";
    
    // The following is needed for input into the delimitString() method
    protected static enum localDBIdentifiers {
        SEQUENCE_GEN_SEQ_NAME,
        SEQUENCE_GEN_SCHEMA
    }    

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

    // Map for storing deferred metadata which needs to be populated
    // after embeddables are loaded.
    private static final Map<Class<?>, ArrayList<MetaDataContext>>
        _embeddables = new HashMap<Class<?>, ArrayList<MetaDataContext>>();
    private static final Map<Class<?>, Integer>
        _embeddableAccess = new HashMap<Class<?>, Integer>();

    static {
        _elems.put(ELEM_PKG, ELEM_PKG);
        _elems.put(ELEM_ACCESS, ELEM_ACCESS);
        _elems.put(ELEM_ATTRS, ELEM_ATTRS);
        _elems.put(ELEM_LISTENER, ELEM_LISTENER);
        _elems.put(ELEM_CASCADE, ELEM_CASCADE);
        _elems.put(ELEM_CASCADE_ALL, ELEM_CASCADE_ALL);
        _elems.put(ELEM_CASCADE_PER, ELEM_CASCADE_PER);
        _elems.put(ELEM_CASCADE_REM, ELEM_CASCADE_REM);
        _elems.put(ELEM_CASCADE_MER, ELEM_CASCADE_MER);
        _elems.put(ELEM_CASCADE_REF, ELEM_CASCADE_REF);
        _elems.put(ELEM_CASCADE_DET, ELEM_CASCADE_DET);
        _elems.put(ELEM_PU_META, ELEM_PU_META);
        _elems.put(ELEM_PU_DEF, ELEM_PU_DEF);
        _elems.put(ELEM_XML_MAP_META_COMPLETE, ELEM_XML_MAP_META_COMPLETE);
        _elems.put(ELEM_DELIM_IDS, ELEM_DELIM_IDS);

        _elems.put("entity-listeners", ENTITY_LISTENERS);
        _elems.put("pre-persist", PRE_PERSIST);
        _elems.put("post-persist", POST_PERSIST);
        _elems.put("pre-remove", PRE_REMOVE);
        _elems.put("post-remove", POST_REMOVE);
        _elems.put("pre-update", PRE_UPDATE);
        _elems.put("post-update", POST_UPDATE);
        _elems.put("post-load", POST_LOAD);
        _elems.put("exclude-default-listeners", EXCLUDE_DEFAULT_LISTENERS);
        _elems.put("exclude-superclass-listeners",
            EXCLUDE_SUPERCLASS_LISTENERS);

        _elems.put("named-query", QUERY);
        _elems.put("named-native-query", NATIVE_QUERY);
        _elems.put("query-hint", QUERY_HINT);
        _elems.put("query", QUERY_STRING);

        _elems.put("flush-mode", FLUSH_MODE);
        _elems.put("sequence-generator", SEQ_GENERATOR);

        _elems.put("id", ID);
        _elems.put("id-class", ID_CLASS);
        _elems.put("embedded-id", EMBEDDED_ID);
        _elems.put("maps-id", MAPPED_BY_ID);
        _elems.put("version", VERSION);
        _elems.put("generated-value", GENERATED_VALUE);
        _elems.put("map-key", MAP_KEY);
        _elems.put("order-by", ORDER_BY);
        _elems.put("order-column", ORDER_COLUMN);
        _elems.put("lob", LOB);

        _elems.put("basic", BASIC);
        _elems.put("many-to-one", MANY_ONE);
        _elems.put("one-to-one", ONE_ONE);
        _elems.put("embedded", EMBEDDED);
        _elems.put("one-to-many", ONE_MANY);
        _elems.put("many-to-many", MANY_MANY);
        _elems.put("transient", TRANSIENT);
        _elems.put("element-collection", ELEM_COLL);
        _elems.put("map-key-class", MAP_KEY_CLASS);
    }

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

    private final OpenJPAConfiguration _conf;
    private MetaDataRepository _repos = null;
    private AnnotationPersistenceMetaDataParser _parser = null;
    private ClassLoader _envLoader = null;
    private int _mode = MODE_NONE;
    private boolean _override = false;

    private final Stack<Object> _elements = new Stack<Object>();
    private final Stack<Object> _parents = new Stack<Object>();

    private Class<?> _cls = null;
    // List of classes currently being parsed
    private ArrayList<Class<?>> _parseList = new ArrayList<Class<?>>();
    private int _fieldPos = 0;
    private int _clsPos = 0;
    private int _access = AccessCode.UNKNOWN;
    private PersistenceStrategy _strategy = null;
    private Set<CascadeType> _cascades = null;
    private Set<CascadeType> _pkgCascades = null;

    private Class<?> _listener = null;
    private Collection<LifecycleCallbacks>[] _callbacks = null;
    private Collection<Class<?>> _listeners = null;
    private int[] _highs = null;
    private boolean _isXMLMappingMetaDataComplete = false;

    private String _ormVersion;
    private String _schemaLocation;

    private static final String ORM_XSD_1_0 = "orm_1_0.xsd";
    private static final String ORM_XSD_2_0 = "orm_2_0.xsd";

    /**
     * Constructor; supply configuration.
     */
    public XMLPersistenceMetaDataParser(OpenJPAConfiguration conf) {
        _conf = conf;
        setValidating(true);
        setLog(conf.getLog(OpenJPAConfiguration.LOG_METADATA));
        setParseComments(true);
        setMode(MODE_META | MODE_MAPPING | MODE_QUERY);
        setParseText(true);
    }

    /**
     * Configuration supplied on construction.
     */
    public OpenJPAConfiguration getConfiguration() {
        return _conf;
    }

    /**
     * The annotation parser. When class is discovered in an XML file,
     * we first parse any annotations present, then override with the XML.
     */
    public AnnotationPersistenceMetaDataParser getAnnotationParser() {
        return _parser;
    }

    /**
     * The annotation parser. When class is discovered in an XML file,
     * we first parse any annotations present, then override with the XML.
     */
    public void setAnnotationParser(AnnotationPersistenceMetaDataParser parser){
        _parser = parser;
    }

    /**
     * Returns the repository for this parser. If none has been set, creates
     * a new repository and sets it.
     */
    public MetaDataRepository getRepository() {
        if (_repos == null) {
            MetaDataRepository repos = _conf.newMetaDataRepositoryInstance();
            MetaDataFactory mdf = repos.getMetaDataFactory();
            if (mdf instanceof DelegatingMetaDataFactory)
                mdf = ((DelegatingMetaDataFactory) mdf).getInnermostDelegate();
            if (mdf instanceof PersistenceMetaDataFactory)
                ((PersistenceMetaDataFactory) mdf).setXMLParser(this);
            _repos = repos;
        }
        return _repos;
    }

    /**
     * Set the metadata repository for this parser.
     */
    public void setRepository(MetaDataRepository repos) {
        _repos = repos;
        if (repos != null
            && (repos.getValidate() & MetaDataRepository.VALIDATE_RUNTIME) != 0)
            setParseComments(false);
    }

    /**
     * Return the environmental class loader to pass on to parsed
     * metadata instances.
     */
    public ClassLoader getEnvClassLoader() {
        return _envLoader;
    }

    /**
     * Set the environmental class loader to pass on to parsed
     * metadata instances.
     */
    public void setEnvClassLoader(ClassLoader loader) {
        _envLoader = loader;
    }

    /**
     * Whether to allow later parses of mapping information to override
     * earlier information for the same class. Defaults to false. Useful
     * when a tool is mapping a class, so that .jdo file partial mapping
     * information can be used even when mappings are stored in .orm files
     * or other locations.
     */
    public boolean getMappingOverride() {
        return _override;
    }

    /**
     * Whether to allow later parses of mapping information to override
     * earlier information for the same class. Defaults to false. Useful
     * when a tool is mapping a class, so that .jdo file partial mapping
     * information can be used even when mappings are stored in .orm files
     * or other locations.
     */
    public void setMappingOverride(boolean override) {
        _override = override;
    }

    /**
     * The parse mode according to the expected document type. The
     * mode constants act as bit flags, and therefore can be combined.
     */
    public int getMode() {
        return _mode;
    }

    /**
     * The parse mode according to the expected document type.
     */
    public void setMode(int mode, boolean on) {
        if (mode == MODE_NONE)
            setMode(MODE_NONE);
        else if (on)
            setMode(_mode | mode);
        else
            setMode(_mode & ~mode);
    }

    /**
     * The parse mode according to the expected document type.
     */
    public void setMode(int mode) {
        _mode = mode;
        if (_parser != null)
            _parser.setMode(mode);
    }

    public void parse(URL url) throws IOException {
        // peek at the doc to determine the version
        XMLVersionParser vp = new XMLVersionParser("entity-mappings");
        try {
            vp.parse(url);
            _ormVersion = vp.getVersion();
            _schemaLocation = vp.getSchemaLocation();
        } catch (Throwable t) {
                Log log = getLog();
                if (log.isInfoEnabled())
                    log.trace(_loc.get("version-check-error",
                        url.toString()));
        }
        super.parse(url);
    }

    public void parse(File file) throws IOException {
        // peek at the doc to determine the version
        XMLVersionParser vp = new XMLVersionParser("entity-mappings");
        try {
            vp.parse(file);
            _ormVersion = vp.getVersion();
            _schemaLocation = vp.getSchemaLocation();
        } catch (Throwable t) {
                Log log = getLog();
                if (log.isInfoEnabled())
                    log.trace(_loc.get("version-check-error",
                        file.toString()));
        }
        super.parse(file);
    }
    /**
     * Convenience method for interpreting {@link #getMode}.
     */
    protected boolean isMetaDataMode() {
        return (_mode & MODE_META) != 0;
    }

    /**
     * Convenience method for interpreting {@link #getMode}.
     */
    protected boolean isQueryMode() {
        return (_mode & MODE_QUERY) != 0;
    }

    /**
     * Convenience method for interpreting {@link #getMode}.
     */
    protected boolean isMappingMode() {
        return (_mode & MODE_MAPPING) != 0;
    }

    /**
     * Returns true if we're in mapping mode or in metadata mode with
     * mapping override enabled.
     */
    protected boolean isMappingOverrideMode() {
        return isMappingMode() || (_override && isMetaDataMode());
    }

    ///////////////
    // XML parsing
    ///////////////

    /**
     * Push a parse element onto the stack.
     */
    protected void pushElement(Object elem) {
        _elements.push(elem);
    }

    /**
     * Pop a parse element from the stack.
     */
    protected Object popElement() {
        return _elements.pop();
    }

    /**
     * Peek a parse element from the stack.
     */
    protected Object peekElement() {
        return _elements.peek();
    }

    /**
     * Return the current element being parsed. May be a class metadata,
     * field metadata, query metadata, etc.
     */
    protected Object currentElement() {
        if (_elements.isEmpty())
            return null;
        return _elements.peek();
    }

    /**
     * Return the current {@link PersistenceStrategy} if any.
     */
    protected PersistenceStrategy currentStrategy() {
        return _strategy;
    }

    /**
     * Return the tag of the current parent element.
     */
    protected Object currentParent() {
        if (_parents.isEmpty())
            return null;
        return _parents.peek();
    }

    /**
     * Return whether we're running the parser at runtime.
     */
    protected boolean isRuntime() {
        return (getRepository().getValidate()
            & MetaDataRepository.VALIDATE_RUNTIME) != 0;
    }

    @Override
    protected Object getSchemaSource() {
        // use the latest schema by default.  'unknown' docs should parse
        // with the latest schema.
        String ormxsd = "orm_2_0-xsd.rsrc";
        // if the version and/or schema location is for 1.0, use the 1.0
        // schema
        if (_ormVersion != null &&
            _ormVersion.equals(XMLVersionParser.VERSION_1_0) ||
            (_schemaLocation != null &&
            _schemaLocation.indexOf(ORM_XSD_1_0) != -1)) {
            ormxsd = "orm-xsd.rsrc";
        }
        return XMLPersistenceMetaDataParser.class.getResourceAsStream(ormxsd);
    }

    @Override
    protected String getPackageAttributeName() {
        return null;
    }

    @Override
    protected String getClassAttributeName() {
        return "class";
    }

    @Override
    protected int getClassElementDepth() {
        return 1;
    }

    @Override
    protected boolean isClassElementName(String name) {
        return "entity".equals(name)
            || "embeddable".equals(name)
            || "mapped-superclass".equals(name);
    }

    @Override
    protected void reset() {
    	// Add all remaining deferred embeddable metadata
        addDeferredEmbeddableMetaData();

        super.reset();
        _elements.clear();
        _parents.clear();
        _cls = null;
        _parseList.clear();
        _fieldPos = 0;
        _clsPos = 0;

        _access = AccessCode.UNKNOWN;
        _strategy = null;
        _listener = null;
        _callbacks = null;
        _highs = null;
        _cascades = null;
        _pkgCascades = null;
    }

    @Override
    protected boolean startSystemElement(String name, Attributes attrs)
        throws SAXException {
        Object tag = (Object) _elems.get(name);
        boolean ret = false;
        if (tag == null) {
            if (isMappingOverrideMode())
                tag = startSystemMappingElement(name, attrs);
            ret = tag != null;
        } else if (tag instanceof MetaDataTag) {
            switch ((MetaDataTag) tag) {
                case QUERY:
                    ret = startNamedQuery(attrs);
                    break;
                case QUERY_HINT:
                    ret = startQueryHint(attrs);
                    break;
                case NATIVE_QUERY:
                    ret = startNamedNativeQuery(attrs);
                    break;
                case QUERY_STRING:
                    ret = startQueryString(attrs);
                    break;
                case SEQ_GENERATOR:
                    ret = startSequenceGenerator(attrs);
                    break;
                case FLUSH_MODE:
                    ret = startFlushMode(attrs);
                    break;
                case ENTITY_LISTENERS:
                    ret = startEntityListeners(attrs);
                    break;
                case PRE_PERSIST:
                case POST_PERSIST:
                case PRE_REMOVE:
                case POST_REMOVE:
                case PRE_UPDATE:
                case POST_UPDATE:
                case POST_LOAD:
                    ret = startCallback((MetaDataTag) tag, attrs);
                    break;
                default:
                    warnUnsupportedTag(name);
            }
        } else if (tag == ELEM_PU_META || tag == ELEM_PU_DEF)
            ret = isMetaDataMode();
        else if (tag == ELEM_XML_MAP_META_COMPLETE) {
            setAnnotationParser(null);
            _isXMLMappingMetaDataComplete = true;
        }
        else if (tag == ELEM_ACCESS)
            ret = _mode != MODE_QUERY;
        else if (tag == ELEM_LISTENER)
            ret = startEntityListener(attrs);
        else if (tag == ELEM_DELIM_IDS)
            ret = startDelimitedIdentifiers();
        else if (tag == ELEM_CASCADE)
            ret = isMetaDataMode();
        else if (tag == ELEM_CASCADE_ALL || tag == ELEM_CASCADE_PER
            || tag == ELEM_CASCADE_MER || tag == ELEM_CASCADE_REM
            || tag == ELEM_CASCADE_REF || tag == ELEM_CASCADE_DET)
            ret = startCascade(tag, attrs);

        if (ret)
            _parents.push(tag);
        return ret;
    }

    @Override
    protected void endSystemElement(String name)
        throws SAXException {
        Object tag = _elems.get(name);
        if (tag == null && isMappingOverrideMode())
            endSystemMappingElement(name);
        else if (tag instanceof MetaDataTag) {
            switch ((MetaDataTag) tag) {
                case QUERY:
                    endNamedQuery();
                    break;
                case QUERY_HINT:
                    endQueryHint();
                    break;
                case NATIVE_QUERY:
                    endNamedNativeQuery();
                    break;
                case QUERY_STRING:
                    endQueryString();
                    break;
                case SEQ_GENERATOR:
                    endSequenceGenerator();
                    break;
            }
        } else if (tag == ELEM_ACCESS)
            endAccess();
        else if (tag == ELEM_LISTENER)
            endEntityListener();

        _parents.pop();
    }

    /**
     * Implement to parse a mapping element outside of any class.
     *
     * @return the tag for the given element, or null to skip the element
     */
    protected Object startSystemMappingElement(String name, Attributes attrs)
        throws SAXException {
        return null;
    }

    /**
     * Implement to parse a mapping element outside of any class.
     */
    protected void endSystemMappingElement(String name)
        throws SAXException {
    }

    @Override
    protected boolean startClassElement(String name, Attributes attrs)
        throws SAXException {
        Object tag = (Object) _elems.get(name);
        boolean ret = false;
        if (tag == null) {
            if (isMappingOverrideMode())
                tag = startClassMappingElement(name, attrs);
            ret = tag != null;
        } else if (tag instanceof MetaDataTag) {
            switch ((MetaDataTag) tag) {
                case GENERATED_VALUE:
                    ret = startGeneratedValue(attrs);
                    break;
                case ID:
                    ret = startId(attrs);
                    break;
                case EMBEDDED_ID:
                    ret = startEmbeddedId(attrs);
                    break;
                case ID_CLASS:
                    ret = startIdClass(attrs);
                    break;
                case LOB:
                    ret = startLob(attrs);
                    break;
                case QUERY:
                    ret = startNamedQuery(attrs);
                    break;
                case QUERY_HINT:
                    ret = startQueryHint(attrs);
                    break;
                case NATIVE_QUERY:
                    ret = startNamedNativeQuery(attrs);
                    break;
                case QUERY_STRING:
                    ret = startQueryString(attrs);
                    break;
                case SEQ_GENERATOR:
                    ret = startSequenceGenerator(attrs);
                    break;
                case VERSION:
                    ret = startVersion(attrs);
                    break;
                case MAP_KEY:
                    ret = startMapKey(attrs);
                    break;
                case MAP_KEY_CLASS:
                    ret = startMapKeyClass(attrs);
                    break;
                case FLUSH_MODE:
                    ret = startFlushMode(attrs);
                    break;
                case ORDER_COLUMN:
                    ret = startOrderColumn(attrs);
                    break;
                case ORDER_BY:
                case ENTITY_LISTENERS:
                    ret = isMetaDataMode();
                    break;
                case EXCLUDE_DEFAULT_LISTENERS:
                    ret = startExcludeDefaultListeners(attrs);
                    break;
                case EXCLUDE_SUPERCLASS_LISTENERS:
                    ret = startExcludeSuperclassListeners(attrs);
                    break;
                case PRE_PERSIST:
                case POST_PERSIST:
                case PRE_REMOVE:
                case POST_REMOVE:
                case PRE_UPDATE:
                case POST_UPDATE:
                case POST_LOAD:
                    ret = startCallback((MetaDataTag) tag, attrs);
                    break;
                default:
                    warnUnsupportedTag(name);
            }
        } else if (tag instanceof PersistenceStrategy) {
            PersistenceStrategy ps = (PersistenceStrategy) tag;
            ret = startStrategy(ps, attrs);
            if (ret)
                _strategy = ps;
        } else if (tag == ELEM_LISTENER)
            ret = startEntityListener(attrs);
        else if (tag == ELEM_ATTRS)
            ret = _mode != MODE_QUERY;
        else if (tag == ELEM_CASCADE)
            ret = isMetaDataMode();
        else if (tag == ELEM_CASCADE_ALL || tag == ELEM_CASCADE_PER
            || tag == ELEM_CASCADE_MER || tag == ELEM_CASCADE_REM
            || tag == ELEM_CASCADE_REF || tag == ELEM_CASCADE_DET)
            ret = startCascade(tag, attrs);

        if (ret)
            _parents.push(tag);
        return ret;
    }

    @Override
    protected void endClassElement(String name)
        throws SAXException {
        Object tag = _elems.get(name);
        if (tag == null && isMappingOverrideMode())
            endClassMappingElement(name);
        else if (tag instanceof MetaDataTag) {
            switch ((MetaDataTag) tag) {
                case GENERATED_VALUE:
                    endGeneratedValue();
                    break;
                case ID:
                    endId();
                    break;
                case EMBEDDED_ID:
                    endEmbeddedId();
                    break;
                case ID_CLASS:
                    endIdClass();
                    break;
                case LOB:
                    endLob();
                    break;
                case QUERY:
                    endNamedQuery();
                    break;
                case QUERY_HINT:
                    endQueryHint();
                    break;
                case NATIVE_QUERY:
                    endNamedNativeQuery();
                    break;
                case QUERY_STRING:
                    endQueryString();
                    break;
                case SEQ_GENERATOR:
                    endSequenceGenerator();
                    break;
                case VERSION:
                    endVersion();
                    break;
                case ORDER_BY:
                    endOrderBy();
                    break;
            }
        } else if (tag instanceof PersistenceStrategy)
            endStrategy((PersistenceStrategy) tag);
        else if (tag == ELEM_ACCESS)
            endAccess();
        else if (tag == ELEM_LISTENER)
            endEntityListener();

        _parents.pop();
    }

    /**
     * Log warning about an unsupported tag.
     */
    private void warnUnsupportedTag(String name) {
        Log log = getLog();
        if (log.isInfoEnabled())
            log.trace(_loc.get("unsupported-tag", name));
    }

    /**
     * Implement to parse a mapping element within a class.
     *
     * @return the tag for the given element, or null to skip element
     */
    protected Object startClassMappingElement(String name, Attributes attrs)
        throws SAXException {
        return null;
    }

    /**
     * Implement to parse a mapping element within a class.
     */
    protected void endClassMappingElement(String name)
        throws SAXException {
    }

    boolean isMetaDataComplete(Attributes attrs) {
    	return attrs != null
    	    && "true".equals(attrs.getValue("metadata-complete"));
    }

    void resetAnnotationParser() {
    	setAnnotationParser(((PersistenceMetaDataFactory)getRepository()
    			.getMetaDataFactory()).getAnnotationParser());
    }

    @Override
    protected boolean startClass(String elem, Attributes attrs)
        throws SAXException {
        boolean metaDataComplete = false;
        super.startClass(elem, attrs);
        if (isMetaDataComplete(attrs)) {
            metaDataComplete = true;
        	setAnnotationParser(null);
        } else if (!_isXMLMappingMetaDataComplete){
        	resetAnnotationParser();
        }

        // query mode only?
        _cls = classForName(currentClassName());

        // Prevent a reentrant parse for the same class
        if (parseListContains(_cls)) {
            return false;
        }

        if (_mode == MODE_QUERY) {
            if (_parser != null)
                _parser.parse(_cls);
            return true;
        }

        Log log = getLog();
        if (log.isTraceEnabled())
            log.trace(_loc.get("parse-class", _cls.getName()));

        MetaDataRepository repos = getRepository();
        ClassMetaData meta = repos.getCachedMetaData(_cls);
        if (meta != null
            && ((isMetaDataMode() && (meta.getSourceMode() & MODE_META) != 0)
            || (isMappingMode() && (meta.getSourceMode() & MODE_MAPPING) != 0)))
        {
            if (log.isWarnEnabled())
                log.warn(_loc.get("dup-metadata", _cls, getSourceName()));
            _cls = null;
            return false;
        }

        int access = AccessCode.UNKNOWN;
        if (meta == null) {
            int accessCode = toAccessType(attrs.getValue("access"));
            // if access not specified and access was specified at
            // the system level, use the system default (which may
            // be UNKNOWN)
            if (accessCode == AccessCode.UNKNOWN)
                accessCode = _access;
            meta = repos.addMetaData(_cls, accessCode, metaDataComplete);
            FieldMetaData[] fmds = meta.getFields();
            if (metaDataComplete) {
                for (int i = 0; i < fmds.length; i++) {
                    fmds[i].setExplicit(true);
                }
            }
            meta.setEnvClassLoader(_envLoader);
            meta.setSourceMode(MODE_NONE);

            // parse annotations first so XML overrides them
            if (_parser != null)
                _parser.parse(_cls);
        }
        access = meta.getAccessType();

        boolean mappedSuper = "mapped-superclass".equals(elem);
        boolean embeddable = "embeddable".equals(elem);
        if (isMetaDataMode()) {
            meta.setSource(getSourceFile(), SourceTracker.SRC_XML);
            meta.setSourceMode(MODE_META, true);
            Locator locator = getLocation().getLocator();
            if (locator != null) {
                meta.setLineNumber(locator.getLineNumber());
                meta.setColNumber(locator.getColumnNumber());
            }
            meta.setListingIndex(_clsPos);
            String name = attrs.getValue("name");
            if (!StringUtils.isEmpty(name))
                meta.setTypeAlias(name);
            meta.setAbstract(mappedSuper);
            meta.setEmbeddedOnly(mappedSuper || embeddable);

            if (embeddable) {
                meta.setEmbeddable();
                setDeferredEmbeddableAccessType(_cls, access);
            }
        }

        if (attrs.getValue("cacheable") != null) {
            meta.setCacheEnabled(Boolean.valueOf(attrs.getValue("cacheable")));
        }

        if (isMappingMode())
            meta.setSourceMode(MODE_MAPPING, true);
        if (isMappingOverrideMode())
            startClassMapping(meta, mappedSuper, attrs);
        if (isQueryMode())
            meta.setSourceMode(MODE_QUERY, true);

        _clsPos++;
        _fieldPos = 0;
        addComments(meta);
        pushElement(meta);
        return true;
    }

    @Override
    protected void endClass(String elem)
        throws SAXException {
        if (_mode != MODE_QUERY) {
            ClassMetaData meta = (ClassMetaData) popElement();
            storeCallbacks(meta);
            if (isMappingOverrideMode())
                endClassMapping(meta);
        }
        _cls = null;
        super.endClass(elem);
    }

    /**
     * Implement to add mapping attributes to class.
     */
    protected void startClassMapping(ClassMetaData mapping,
        boolean mappedSuper, Attributes attrs)
        throws SAXException {
    }

    /**
     * Implement to finalize class mapping.
     */
    protected void endClassMapping(ClassMetaData mapping)
        throws SAXException {
    }

    /**
     * Default access element.
     */
    private void endAccess() {
        _access = toAccessType(currentText());
    }

    /**
     * Parse the given string as an entity access type, defaulting to given
     * default if string is empty.
     */
    private int toAccessType(String str) {
        if (StringUtils.isEmpty(str))
            return AccessCode.UNKNOWN;
        if ("PROPERTY".equals(str))
            return AccessCode.EXPLICIT | AccessCode.PROPERTY;
        return AccessCode.EXPLICIT | AccessCode.FIELD;
    }
    /**
     * Parse flush-mode element.
     */
    private boolean startFlushMode(Attributes attrs)
        throws SAXException {
        Log log = getLog();
        if (log.isWarnEnabled())
            log.warn(_loc.get("unsupported", "flush-mode", getSourceName()));
        return false;
    }

    /**
     * Parse sequence-generator.
     */
    protected boolean startSequenceGenerator(Attributes attrs) {
        if (!isMappingOverrideMode())
            return false;

        String name = attrs.getValue("name");
        Log log = getLog();
        if (log.isTraceEnabled())
            log.trace(_loc.get("parse-sequence", name));

        SequenceMetaData meta = getRepository().getCachedSequenceMetaData(name);
        if (meta != null && log.isWarnEnabled())
            log.warn(_loc.get("override-sequence", name));

        meta = getRepository().addSequenceMetaData(name);
        String seq = attrs.getValue("sequence-name");
        // Do not normalize the sequence name if it appears to be a plugin 
        if (seq.indexOf('(') == -1){
            seq = normalizeSequenceName(seq);
        }
        String val = attrs.getValue("initial-value");
        int initial = val == null ? 1 : Integer.parseInt(val);
        val = attrs.getValue("allocation-size");
        int allocate = val == null ? 50 : Integer.parseInt(val);
        String schema = normalizeSchemaName(attrs.getValue("schema"));
        String catalog = normalizeCatalogName(attrs.getValue("catalog"));

        String clsName, props;
        if (seq == null || seq.indexOf('(') == -1) {
            clsName = SequenceMetaData.IMPL_NATIVE;
            props = null;
        } else { // plugin
            clsName = Configurations.getClassName(seq);
            props = Configurations.getProperties(seq);
            seq = null;
        }

        meta.setSequencePlugin(Configurations.getPlugin(clsName, props));
        meta.setSequence(seq);
        meta.setInitialValue(initial);
        meta.setAllocate(allocate);
        meta.setSchema(schema);
        meta.setCatalog(catalog);

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

    protected void endSequenceGenerator() {
    }

    /**
     * Parse id.
     */
    protected boolean startId(Attributes attrs)
        throws SAXException {
        FieldMetaData fmd = parseField(attrs);
        fmd.setExplicit(true);
        fmd.setPrimaryKey(true);
        return true;
    }

    protected void endId()
        throws SAXException {
        finishField();
    }

    /**
     * Parse embedded-id.
     */
    protected boolean startEmbeddedId(Attributes attrs)
        throws SAXException {
        FieldMetaData fmd = parseField(attrs);
        fmd.setExplicit(true);
        fmd.setPrimaryKey(true);
        fmd.setEmbedded(true);
        fmd.setSerialized(false);
        if (fmd.getEmbeddedMetaData() == null)
//            fmd.addEmbeddedMetaData();
            deferEmbeddable(fmd.getDeclaredType(), fmd);
        return true;
    }

    protected void endEmbeddedId()
        throws SAXException {
        finishField();
    }

    /**
     * Parse id-class.
     */
    protected boolean startIdClass(Attributes attrs)
        throws SAXException {
        if (!isMetaDataMode())
            return false;

        ClassMetaData meta = (ClassMetaData) currentElement();
        String cls = attrs.getValue("class");
        Class<?> idCls = null;
        try {
            idCls = classForName(cls);
        } catch (Throwable t) {
            throw getException(_loc.get("invalid-id-class", meta, cls), t);
        }
        meta.setObjectIdType(idCls, true);
        return true;
    }

    protected void endIdClass()
        throws SAXException {
    }

    /**
     * Parse lob.
     */
    protected boolean startLob(Attributes attrs)
        throws SAXException {
        FieldMetaData fmd = (FieldMetaData) currentElement();
        int typeCode = fmd.isElementCollection() ? fmd.getElement().getDeclaredTypeCode() : fmd.getDeclaredTypeCode();
        Class<?> type = fmd.isElementCollection() ? fmd.getElement().getDeclaredType() : fmd.getDeclaredType();
        if (typeCode != JavaTypes.STRING
            && type != char[].class
            && type != Character[].class
            && type != byte[].class
            && type != Byte[].class)
            fmd.setSerialized(true);
        return true;
    }

    protected void endLob()
        throws SAXException {
    }

    /**
     * Parse generated-value.
     */
    protected boolean startGeneratedValue(Attributes attrs)
        throws SAXException {
        if (!isMappingOverrideMode())
            return false;

        String strategy = attrs.getValue("strategy");
        String generator = attrs.getValue("generator");
        GenerationType type = StringUtils.isEmpty(strategy)
            ? GenerationType.AUTO : GenerationType.valueOf(strategy);

        FieldMetaData fmd = (FieldMetaData) currentElement();
        AnnotationPersistenceMetaDataParser.parseGeneratedValue(fmd, type,
            generator);
        return true;
    }

    protected void endGeneratedValue()
        throws SAXException {
    }

    /**
     * Lazily parse cascades.
     */
    protected boolean startCascade(Object tag, Attributes attrs)
        throws SAXException {
        if (!isMetaDataMode())
            return false;

        Set<CascadeType> cascades = null;
        if (currentElement() instanceof FieldMetaData) {
            if (_cascades == null)
                _cascades = EnumSet.noneOf(CascadeType.class);
            cascades = _cascades;
        } else {
            if (_pkgCascades == null)
                _pkgCascades = EnumSet.noneOf(CascadeType.class);
            cascades = _pkgCascades;
        }
        boolean all = ELEM_CASCADE_ALL == tag;
        if (all || ELEM_CASCADE_PER == tag)
            cascades.add(PERSIST);
        if (all || ELEM_CASCADE_REM == tag)
            cascades.add(REMOVE);
        if (all || ELEM_CASCADE_MER == tag)
            cascades.add(MERGE);
        if (all || ELEM_CASCADE_REF == tag)
            cascades.add(REFRESH);
        if (all || ELEM_CASCADE_DET == tag)
            cascades.add(DETACH);
        return true;
    }

    /**
     * Set the cached cascades into the field.
     */
    protected void setCascades(FieldMetaData fmd) {
        Set<CascadeType> cascades = _cascades;
        if (_cascades == null)
            cascades = _pkgCascades;
        if (cascades == null)
            return;

        ValueMetaData vmd = fmd;
        switch (_strategy) {
            case ONE_MANY:
            case MANY_MANY:
                vmd = fmd.getElement();
        }
        for (CascadeType cascade : cascades) {
            switch (cascade) {
                case PERSIST:
                    vmd.setCascadePersist(ValueMetaData.CASCADE_IMMEDIATE);
                    break;
                case MERGE:
                    vmd.setCascadeAttach(ValueMetaData.CASCADE_IMMEDIATE);
                    break;
                case DETACH:
                    vmd.setCascadeDetach(ValueMetaData.CASCADE_IMMEDIATE);
                    break;
                case REMOVE:
                    vmd.setCascadeDelete(ValueMetaData.CASCADE_IMMEDIATE);
                    break;
                case REFRESH:
                    vmd.setCascadeRefresh(ValueMetaData.CASCADE_IMMEDIATE);
                    break;
            }
        }
        _cascades = null;
    }

    /**
     * Parse common field attributes.
     */
    private FieldMetaData parseField(Attributes attrs)
        throws SAXException {
        ClassMetaData meta = (ClassMetaData) currentElement();
        String name = attrs.getValue("name");
        FieldMetaData field = meta.getDeclaredField(name);
        int fldAccess = getFieldAccess(field, attrs);
        // If the access defined in XML is not the same as what was defined
        // by default or annotation, find the appropriate backing member and
        // replace what is currently defined in metadata.
        if ((field == null || field.getDeclaredType() == Object.class ||
             field.getAccessType() != fldAccess)
            && meta.getDescribedType() != Object.class) {
            Member member = _repos.getMetaDataFactory().getDefaults()
     	        .getMemberByProperty(meta, name, fldAccess, false);
            Class<?> type = Field.class.isInstance(member) ?
                ((Field)member).getType() : ((Method)member).getReturnType();

            if (field == null) {
                field = meta.addDeclaredField(name, type);
                PersistenceMetaDataDefaults.setCascadeNone(field);
                PersistenceMetaDataDefaults.setCascadeNone(field.getKey());
                PersistenceMetaDataDefaults.setCascadeNone(field.getElement());
            }
            field.backingMember(member);
        } else if (field == null) {
            field = meta.addDeclaredField(name, Object.class);
            PersistenceMetaDataDefaults.setCascadeNone(field);
            PersistenceMetaDataDefaults.setCascadeNone(field.getKey());
            PersistenceMetaDataDefaults.setCascadeNone(field.getElement());
        }

        if (isMetaDataMode())
            field.setListingIndex(_fieldPos);

        _fieldPos++;
        pushElement(field);
        addComments(field);

        if (isMappingOverrideMode())
            startFieldMapping(field, attrs);
        return field;
    }

    /**
     * Pops field element.
     */
    private void finishField()
        throws SAXException {
        FieldMetaData field = (FieldMetaData) popElement();
        setCascades(field);
        if (isMappingOverrideMode())
            endFieldMapping(field);
        _strategy = null;
    }

    /**
     * Determines access for field based upon existing metadata and XML
     * attributes.
     *
     * @param field FieldMetaData current metadata for field
     * @param attrs XML Attributes defined on this field
     * @return
     */
    private int getFieldAccess(FieldMetaData field, Attributes attrs) {
        if (attrs != null) {
            String access = attrs.getValue("access");
            if ("PROPERTY".equals(access))
                return AccessCode.EXPLICIT | AccessCode.PROPERTY;
            if ("FIELD".equals(access))
                return AccessCode.EXPLICIT | AccessCode.FIELD;
        }
        // Check access defined on field, if provided
        if (field != null) {
            return field.getAccessType();
        }
        // Otherwise, get the default access type of the declaring class
        ClassMetaData meta = (ClassMetaData) currentElement();
        if (meta != null) {
            return AccessCode.toFieldCode(meta.getAccessType());
        }
        return AccessCode.UNKNOWN;
    }

    /**
     * Implement to add field mapping data. Does nothing by default.
     */
    protected void startFieldMapping(FieldMetaData field, Attributes attrs)
        throws SAXException {
    }

    /**
     * Implement to finalize field mapping. Does nothing by default.
     */
    protected void endFieldMapping(FieldMetaData field)
        throws SAXException {
    }

    /**
     * Parse version.
     */
    protected boolean startVersion(Attributes attrs)
        throws SAXException {
        FieldMetaData fmd = parseField(attrs);
        fmd.setExplicit(true);
        fmd.setVersion(true);
        return true;
    }

    protected void endVersion()
        throws SAXException {
        finishField();
    }

    /**
     * Parse strategy element.
     */
    private boolean startStrategy(PersistenceStrategy strategy,
        Attributes attrs)
        throws SAXException {
        FieldMetaData fmd = parseField(attrs);
        fmd.setExplicit(true);
        fmd.setManagement(FieldMetaData.MANAGE_PERSISTENT);

        String val = attrs.getValue("optional");
        if ("false".equals(val))
            fmd.setNullValue(FieldMetaData.NULL_EXCEPTION);
        else if ("true".equals(val)
                && fmd.getNullValue() == FieldMetaData.NULL_EXCEPTION) {
            // Reset value if the field was annotated with optional=false.
            // Otherwise leave it alone.
            fmd.setNullValue(FieldMetaData.NULL_UNSET);
        }
        if (isMappingOverrideMode()) {
            val = attrs.getValue("mapped-by");
            if (val != null)
                fmd.setMappedBy(val);
        }
        parseStrategy(fmd, strategy, attrs);
        return true;
    }

    private void endStrategy(PersistenceStrategy strategy)
        throws SAXException {
        finishField();
    }

    /**
     * Parse strategy specific attributes.
     */
    private void parseStrategy(FieldMetaData fmd,
        PersistenceStrategy strategy, Attributes attrs)
        throws SAXException {
        switch (strategy) {
            case BASIC:
                parseBasic(fmd, attrs);
                break;
            case EMBEDDED:
                parseEmbedded(fmd, attrs);
                break;
            case ONE_ONE:
                parseOneToOne(fmd, attrs);
                break;
            case MANY_ONE:
                parseManyToOne(fmd, attrs);
                break;
            case MANY_MANY:
                parseManyToMany(fmd, attrs);
                break;
            case ONE_MANY:
                parseOneToMany(fmd, attrs);
                break;
            case TRANSIENT:
                String val = attrs.getValue("fetch");
                if (val != null) {
                    fmd.setInDefaultFetchGroup("EAGER".equals(val));
                }
                fmd.setManagement(FieldMetaData.MANAGE_NONE);
                break;
            case ELEM_COLL:
                parseElementCollection(fmd, attrs);
        }
    }

    /**
     * Parse basic.
     */
    protected void parseBasic(FieldMetaData fmd, Attributes attrs)
        throws SAXException {
        String val = attrs.getValue("fetch");
        if (val != null) {
            fmd.setInDefaultFetchGroup("EAGER".equals(val));
        }
    }

    /**
     * Parse embedded.
     */
    protected void parseEmbedded(FieldMetaData fmd, Attributes attrs)
        throws SAXException {
        assertPC(fmd, "Embedded");
        fmd.setInDefaultFetchGroup(true);
        fmd.setEmbedded(true);
        fmd.setSerialized(false); // override any Lob annotation

        if (fmd.getEmbeddedMetaData() == null)
//            fmd.addEmbeddedMetaData();
            deferEmbeddable(fmd.getDeclaredType(), fmd);
    }

    /**
     * Throw proper exception if given value is not possibly persistence
     * capable.
     */
    private void assertPC(FieldMetaData fmd, String attr)
        throws SAXException {
        if (!JavaTypes.maybePC(fmd))
            throw getException(_loc.get("bad-meta-anno", fmd, attr));
    }

    /**
     * Parse one-to-one.
     */
    protected void parseOneToOne(FieldMetaData fmd, Attributes attrs)
        throws SAXException {
        String val = attrs.getValue("fetch");
        if (val == null || "EAGER".equals(val)) {
            fmd.setInDefaultFetchGroup(true);
        }
        val = attrs.getValue("target-entity");
        if (val != null)
            fmd.setTypeOverride(classForName(val));
        assertPC(fmd, "OneToOne");
        fmd.setSerialized(false); // override any Lob annotation
        boolean orphanRemoval = Boolean.valueOf(attrs.getValue(
            "orphan-removal"));
        setOrphanRemoval(fmd, orphanRemoval);
        String mapsId = attrs.getValue("maps-id");
        if (mapsId != null)
            fmd.setMappedByIdValue(mapsId);
    }

    /**
     * Parse many-to-one.
     */
    protected void parseManyToOne(FieldMetaData fmd, Attributes attrs)
        throws SAXException {
        String val = attrs.getValue("fetch");
        if (val == null || "EAGER".equals(val)) {
            fmd.setInDefaultFetchGroup(true);
        }
        val = attrs.getValue("target-entity");
        if (val != null)
            fmd.setTypeOverride(classForName(val));
        assertPC(fmd, "ManyToOne");
        fmd.setSerialized(false); // override any Lob annotation
        String mapsId = attrs.getValue("maps-id");
        if (mapsId != null)
            fmd.setMappedByIdValue(mapsId);
    }

    /**
     * Parse many-to-many.
     */
    protected void parseManyToMany(FieldMetaData fmd, Attributes attrs)
        throws SAXException {
        String val = attrs.getValue("fetch");
        if (val != null) {
            fmd.setInDefaultFetchGroup("EAGER".equals(val));
        }
        val = attrs.getValue("target-entity");
        if (val != null)
            fmd.getElement().setDeclaredType(classForName(val));
        assertPCCollection(fmd, "ManyToMany");
        fmd.setSerialized(false); // override Lob in annotation
    }

    /**
     * Throw exception if given field not a collection of possible persistence
     * capables.
     */
    private void assertPCCollection(FieldMetaData fmd, String attr)
        throws SAXException {
        switch (fmd.getDeclaredTypeCode()) {
            case JavaTypes.ARRAY:
            case JavaTypes.COLLECTION:
            case JavaTypes.MAP:
                if (JavaTypes.maybePC(fmd.getElement()))
                    break;
                // no break
            default:
                throw getException(_loc.get("bad-meta-anno", fmd, attr));
        }
    }

    /**
     * Parse one-to-many.
     */
    protected void parseOneToMany(FieldMetaData fmd, Attributes attrs)
        throws SAXException {
        String val = attrs.getValue("fetch");
        if (val != null) {
            fmd.setInDefaultFetchGroup("EAGER".equals(val));
        }
        val = attrs.getValue("target-entity");
        if (val != null)
            fmd.getElement().setDeclaredType(classForName(val));
        assertPCCollection(fmd, "OneToMany");
        fmd.setSerialized(false); // override any Lob annotation
        boolean orphanRemoval = Boolean.valueOf(attrs.getValue(
            "orphan-removal"));
        setOrphanRemoval(fmd.getElement(), orphanRemoval);
    }

    protected void setOrphanRemoval(ValueMetaData vmd, boolean orphanRemoval) {
        if (orphanRemoval)
            vmd.setCascadeDelete(ValueMetaData.CASCADE_AUTO);
    }

    protected void parseElementCollection(FieldMetaData fmd, Attributes attrs)
        throws SAXException {
        String val = attrs.getValue("target-class");
        if (val != null)
            fmd.getElement().setDeclaredType(classForName(val));

        if (fmd.getDeclaredTypeCode() != JavaTypes.COLLECTION &&
            fmd.getDeclaredTypeCode() != JavaTypes.MAP)
            throw getException(_loc.get("bad-meta-anno", fmd,
                    "ElementCollection"));

        val = attrs.getValue("fetch");
        if (val != null)
            fmd.setInDefaultFetchGroup("EAGER".equals(val));
        fmd.setElementCollection(true);
        fmd.setSerialized(false);
        if (JavaTypes.maybePC(fmd.getElement()) && !fmd.getElement().getDeclaredType().isEnum()) {
            fmd.getElement().setEmbedded(true);
            if (fmd.getElement().getEmbeddedMetaData() == null)
//                fmd.getElement().addEmbeddedMetaData();
                deferEmbeddable(fmd.getElement().getDeclaredType(),
                    fmd.getElement());
        }
    }

    /**
     * Parse map-key.
     */
    private boolean startMapKey(Attributes attrs)
        throws SAXException {
        if (!isMappingOverrideMode())
            return false;

        FieldMetaData fmd = (FieldMetaData) currentElement();
        String mapKey = attrs.getValue("name");
        if (mapKey == null)
            fmd.getKey().setValueMappedBy(ValueMetaData.MAPPED_BY_PK);
        else
            fmd.getKey().setValueMappedBy(mapKey);
        return true;
    }


    /**
     * Parse map-key-class.
     */
    private boolean startMapKeyClass(Attributes attrs)
    throws SAXException {
        if (!isMappingOverrideMode())
            return false;

        FieldMetaData fmd = (FieldMetaData) currentElement();
        String mapKeyClass = attrs.getValue("class");

        if (mapKeyClass != null) {
            try {
                fmd.getKey().setDeclaredType(Class.forName(mapKeyClass));
            } catch (ClassNotFoundException e) {
                throw new IllegalArgumentException("Class not found");
            }
        } else
            throw new IllegalArgumentException(
            "The value of the MapKeyClass cannot be null");
        return true;
    }

    /**
     * Parse order-by.
     */
    private void endOrderBy()
        throws SAXException {
        FieldMetaData fmd = (FieldMetaData) currentElement();
        String dec = currentText();
        if (fmd.isElementCollection() &&
            fmd.getElement().getEmbeddedMetaData() != null ||
            isDeferredEmbeddable(fmd.getElement().getDeclaredType(),
                fmd.getElement())) {
            if (dec.length() == 0 || dec.equals("ASC") ||
                dec.equals("DESC"))
                throw new MetaDataException(_loc.get(
                    "invalid-orderBy", fmd));
        }
        if (StringUtils.isEmpty(dec) || dec.equals("ASC"))
            dec = Order.ELEMENT + " asc";
        else if (dec.equals("DESC"))
            dec = Order.ELEMENT + " desc";

        fmd.setOrderDeclaration(dec);
    }

    /**
     * Parse named-query.
     */
    protected boolean startNamedQuery(Attributes attrs)
        throws SAXException {
        if (!isQueryMode())
            return false;

        String name = attrs.getValue("name");
        Log log = getLog();
        if (log.isTraceEnabled())
            log.trace(_loc.get("parse-query", name));

        QueryMetaData meta = getRepository().searchQueryMetaDataByName(name);
        if (meta != null) {
        	Class<?> defType = meta.getDefiningType();
            if ((defType != _cls) && log.isWarnEnabled()) {
                log.warn(_loc.get("dup-query", name, currentLocation(),
            	        defType));
            }
            pushElement(meta);
            return true;
        }

        meta = getRepository().addQueryMetaData(null, name);
        meta.setDefiningType(_cls);
        meta.setQueryString(attrs.getValue("query"));
        meta.setLanguage(JPQLParser.LANG_JPQL);
        String lockModeStr = attrs.getValue("lock-mode");
        LockModeType lmt = processNamedQueryLockModeType(log, lockModeStr, name);
        if (lmt != null) {
            meta.addHint("openjpa.FetchPlan.ReadLockMode", lmt);
        }
        Locator locator = getLocation().getLocator();
        if (locator != null) {
            meta.setLineNumber(locator.getLineNumber());
            meta.setColNumber(locator.getColumnNumber());
        }
        Object cur = currentElement();
        Object scope = (cur instanceof ClassMetaData)
            ? ((ClassMetaData) cur).getDescribedType() : null;
        meta.setSource(getSourceFile(), scope, SourceTracker.SRC_XML);
        if (isMetaDataMode())
            meta.setSourceMode(MODE_META);
        else if (isMappingMode())
            meta.setSourceMode(MODE_MAPPING);
        else
            meta.setSourceMode(MODE_QUERY);
        pushElement(meta);
        return true;
    }
    
    /**
     * A private worker method that calculates the lock mode for an individual NamedQuery. If the NamedQuery is 
     * configured to use the NONE lock mode(explicit or implicit), this method will promote the lock to a READ
     * level lock. This was done to allow for JPA1 apps to function properly under a 2.0 runtime. 
     */
    private LockModeType processNamedQueryLockModeType(Log log, String lockModeString, String queryName) {
        if (lockModeString == null) {
            return null;
        }
        LockModeType lmt = LockModeType.valueOf(lockModeString);
        String lm = _conf.getLockManager();
        if (lm != null) {
            lm = lm.toLowerCase();
            if (lm.contains("pessimistic")) {
                if (lmt == LockModeType.NONE) {
                    if (log != null && log.isWarnEnabled() == true) {
                        log.warn(_loc.get("override-named-query-lock-mode", new String[] { "xml", queryName,
                            _cls.getName() }));
                    }
                    lmt = LockModeType.READ;
                }
            }
        }

        return lmt;
    }

    protected void endNamedQuery()
        throws SAXException {
        popElement();
    }

    protected boolean startQueryString(Attributes attrs)
        throws SAXException {
        return true;
    }

    protected void endQueryString()
        throws SAXException {
        QueryMetaData meta = (QueryMetaData) currentElement();
        meta.setQueryString(currentText());
    }

    /**
     * Parse query-hint.
     */
    protected boolean startQueryHint(Attributes attrs)
        throws SAXException {
        QueryMetaData meta = (QueryMetaData) currentElement();
        meta.addHint(attrs.getValue("name"), attrs.getValue("value"));
        return true;
    }

    protected void endQueryHint()
        throws SAXException {
    }

    /**
     * Parse native-named-query.
     */
    protected boolean startNamedNativeQuery(Attributes attrs)
        throws SAXException {
        if (!isQueryMode())
            return false;

        String name = attrs.getValue("name");
        Log log = getLog();
        if (log.isTraceEnabled())
            log.trace(_loc.get("parse-native-query", name));

        QueryMetaData meta = getRepository().getCachedQueryMetaData(null, name);
        if (meta != null && log.isWarnEnabled())
            log.warn(_loc.get("override-query", name, currentLocation()));

        meta = getRepository().addQueryMetaData(null, name);
        meta.setDefiningType(_cls);
        meta.setQueryString(attrs.getValue("query"));
        meta.setLanguage(QueryLanguages.LANG_SQL);
        String val = attrs.getValue("result-class");
        if (val != null) {
            Class<?> type = classForName(val);
            if (ImplHelper.isManagedType(getConfiguration(), type))
                meta.setCandidateType(type);
            else
                meta.setResultType(type);
        }

        val = attrs.getValue("result-set-mapping");
        if (val != null)
            meta.setResultSetMappingName(val);

        Object cur = currentElement();
        Object scope = (cur instanceof ClassMetaData)
            ? ((ClassMetaData) cur).getDescribedType() : null;
        meta.setSource(getSourceFile(), scope, SourceTracker.SRC_XML);
        Locator locator = getLocation().getLocator();
        if (locator != null) {
            meta.setLineNumber(locator.getLineNumber());
            meta.setColNumber(locator.getColumnNumber());
        }
        if (isMetaDataMode())
            meta.setSourceMode(MODE_META);
        else if (isMappingMode())
            meta.setSourceMode(MODE_MAPPING);
        else
            meta.setSourceMode(MODE_QUERY);
        pushElement(meta);
        return true;
    }

    protected void endNamedNativeQuery()
        throws SAXException {
        popElement();
    }

    /**
     * Start entity-listeners
     */
    private boolean startEntityListeners(Attributes attrs)
        throws SAXException {
        if (!isMetaDataMode())
            return false;
        if (currentElement() == null)
            return true;

        // reset listeners declared in annotations.
        LifecycleMetaData meta = ((ClassMetaData) currentElement()).
            getLifecycleMetaData();
        for (int i = 0; i < LifecycleEvent.ALL_EVENTS.length; i++)
            meta.setDeclaredCallbacks(i, null, 0);
        return true;
    }

    /**
     * Parse exclude-default-listeners.
     */
    private boolean startExcludeDefaultListeners(Attributes attrs)
        throws SAXException {
        if (!isMetaDataMode())
            return false;
        ClassMetaData meta = (ClassMetaData) currentElement();
        meta.getLifecycleMetaData().setIgnoreSystemListeners(true);
        return true;
    }

    /**
     * Parse exclude-superclass-listeners.
     */
    private boolean startExcludeSuperclassListeners(Attributes attrs)
        throws SAXException {
        if (!isMetaDataMode())
            return false;
        ClassMetaData meta = (ClassMetaData) currentElement();
        meta.getLifecycleMetaData().setIgnoreSuperclassCallbacks
            (LifecycleMetaData.IGNORE_HIGH);
        return true;
    }

    /**
     * Parse entity-listener.
     */
    private boolean startEntityListener(Attributes attrs)
        throws SAXException {
        _listener = classForName(attrs.getValue("class"));
        if (!_conf.getCallbackOptionsInstance().getAllowsDuplicateListener()) {
            if (_listeners == null)
                _listeners = new ArrayList<Class<?>>();
            if (_listeners.contains(_listener)) 
                return true;
            _listeners.add(_listener);    
        }
            
        boolean system = currentElement() == null;
        Collection<LifecycleCallbacks>[] parsed =
            AnnotationPersistenceMetaDataParser.parseCallbackMethods(_listener,
                null, true, true, _repos);
        if (parsed == null)
            return true;

        if (_callbacks == null) {
            _callbacks = (Collection<LifecycleCallbacks>[])
                new Collection[LifecycleEvent.ALL_EVENTS.length];
            if (!system)
                _highs = new int[LifecycleEvent.ALL_EVENTS.length];
        }
        for (int i = 0; i < parsed.length; i++) {
            if (parsed[i] == null)
                continue;
            if (_callbacks[i] == null)
                _callbacks[i] = parsed[i];
            else
                _callbacks[i].addAll(parsed[i]);
            if (!system)
                _highs[i] += parsed[i].size();
        }
        return true;
    }

    private void endEntityListener()
        throws SAXException {
        // should be in endEntityListeners I think to merge callbacks
        // into a single listener.  But then the user cannot remove.
        if (currentElement() == null && _callbacks != null) {
            _repos.addSystemListener(new PersistenceListenerAdapter
                (_callbacks));
            _callbacks = null;
        }
        _listener = null;
    }

    private boolean startCallback(MetaDataTag callback, Attributes attrs)
        throws SAXException {
        if (!isMetaDataMode())
            return false;
        int[] events = MetaDataParsers.getEventTypes(callback, _conf);
        if (events == null)
            return false;

        boolean system = currentElement() == null;

        // If in a multi-level parse, do not add system level listeners.
        // Otherwise, they will get added multiple times.
        if (system && _parseList != null && _parseList.size() > 0) {
            return false;
        }

        Class<?> type = currentElement() == null ? null :
            ((ClassMetaData) currentElement()).getDescribedType();
        if (type == null)
            type = Object.class;

        if (_callbacks == null) {
            _callbacks = (Collection<LifecycleCallbacks>[])
                new Collection[LifecycleEvent.ALL_EVENTS.length];
            if (!system)
                _highs = new int[LifecycleEvent.ALL_EVENTS.length];
        }

        LifecycleCallbacks adapter;
        if (_listener != null)
            adapter = new BeanLifecycleCallbacks(_listener,
                attrs.getValue("method-name"), false, type);
        else
            adapter = new MethodLifecycleCallbacks(_cls,
                attrs.getValue("method-name"), false);

        for (int i = 0; i < events.length; i++) {
            int event = events[i];
            if (_listener != null) {
                MetaDataParsers.validateMethodsForSameCallback(_listener,
                    _callbacks[event], ((BeanLifecycleCallbacks) adapter).
                    getCallbackMethod(), callback, _conf, getLog());
            } else {
                MetaDataParsers.validateMethodsForSameCallback(_cls,
                    _callbacks[event], ((MethodLifecycleCallbacks) adapter).
                    getCallbackMethod(), callback, _conf, getLog());

            }
            if (_callbacks[event] == null)
                _callbacks[event] = new ArrayList<LifecycleCallbacks>(3);
            _callbacks[event].add(adapter);
            if (!system && _listener != null)
                _highs[event]++;
        }
        return true;
    }

    /**
     * Store lifecycle metadata.
     */
    private void storeCallbacks(ClassMetaData cls) {
        LifecycleMetaData meta = cls.getLifecycleMetaData();
        Class<?> supCls = cls.getDescribedType().getSuperclass();
        Collection<LifecycleCallbacks>[] supCalls = null;
        if (!Object.class.equals(supCls)) {
            supCalls = AnnotationPersistenceMetaDataParser.parseCallbackMethods
                (supCls, null, true, false, _repos);
        }
        if (supCalls != null) {
            for (int event : LifecycleEvent.ALL_EVENTS) {
                if (supCalls[event] == null)
                    continue;
                meta.setNonPCSuperclassCallbacks(event, supCalls[event].toArray
                    (new LifecycleCallbacks[supCalls[event].size()]), 0);
            }
        }
        if (_callbacks == null)
            return;

        for (int event : LifecycleEvent.ALL_EVENTS) {
            if (_callbacks[event] == null)
                continue;
            meta.setDeclaredCallbacks(event, (LifecycleCallbacks[])
                _callbacks[event].toArray
                    (new LifecycleCallbacks[_callbacks[event].size()]),
                _highs[event]);
        }
        _callbacks = null;
        _highs = null;
    }

    protected boolean startOrderColumn(Attributes attrs)
        throws SAXException {
        return true;
    }

    /**
     * Instantiate the given class, taking into account the default package.
	 */
	protected Class<?> classForName(String name)
		throws SAXException {
		if ("Entity".equals(name))
			return PersistenceCapable.class;
		return super.classForName(name, isRuntime());
	}

	/**
	 * Process all deferred embeddables using an unknown access type.
	 */
	protected void addDeferredEmbeddableMetaData() {
	    if (_embeddables != null && _embeddables.size() > 0) {
	        // Reverse iterate the array of remaining deferred embeddables
	        // since elements will be removed as they are processed.
	        Class<?>[] classes = _embeddables.keySet().toArray(
	            new Class<?>[_embeddables.size()]);
	        for (int i = classes.length - 1 ; i >= 0; i--) {
	            try {
	                Integer access = _embeddableAccess.get(classes[i]);
	                if (access == null) {
	                    access = AccessCode.UNKNOWN;
	                }
	                addDeferredEmbeddableMetaData(classes[i],
	                    access);
	            }
	            catch (Exception e) {
	                throw new MetaDataException(
	                    _loc.get("no-embeddable-metadata",
	                        classes[i].getName()), e);
	            }
	        }
	    }	
	}
	
    /**
     * Process all deferred embeddables and embeddable mapping overrides
     * for a given class.  This should only happen after the access type
     * of the embeddable is known.
     *
     * @param embedType embeddable class
     * @param access class level access for embeddable
     * @throws SAXException
     */
    protected void addDeferredEmbeddableMetaData(Class<?> embedType,
        int access) throws SAXException {
        ArrayList<MetaDataContext> fmds = _embeddables.get(embedType);
        if (fmds != null && fmds.size() > 0) {
            for (MetaDataContext md : fmds) {
                if (md instanceof FieldMetaData) {
                    FieldMetaData fmd = (FieldMetaData)md;
                    fmd.addEmbeddedMetaData(access);
                }
                else if (md instanceof ValueMetaData) {
                    ValueMetaData vmd = (ValueMetaData)md;
                    vmd.addEmbeddedMetaData(access);
                }
            }
            applyDeferredEmbeddableOverrides(embedType);
            // Clean up deferrals after they have been processed
            fmds.clear();
            _embeddables.remove(embedType);
        }
    }
    protected void setDeferredEmbeddableAccessType(Class<?> embedType,
        int access) {
        _embeddableAccess.put(embedType, access);
    }

    /*
     * Clear any deferred metadata
     */
    @Override
    protected void clearDeferredMetaData() {
        _embeddables.clear();
        _embeddableAccess.clear();
    }

    /*
     * Determines whether the embeddable type is deferred.
     */
    protected boolean isDeferredEmbeddable(Class<?> embedType,
        MetaDataContext fmd) {
        ArrayList<MetaDataContext> fmds = _embeddables.get(embedType);
        if (fmds != null) {
            return fmds.contains(fmd);
        }
        return false;
    }

    /*
     * Add the fmd to the defer list for for the given embeddable type
     */
    protected void deferEmbeddable(Class<?> embedType, MetaDataContext fmd) {
        ArrayList<MetaDataContext> fmds = _embeddables.get(embedType);
        if (fmds == null) {
            fmds = new ArrayList<MetaDataContext>();
            _embeddables.put(embedType, fmds);
        }
        fmds.add(fmd);
    }

    /*
     * Apply any deferred overrides.
     */
    protected void applyDeferredEmbeddableOverrides(Class<?> cls)
        throws SAXException {
    }

	/*
	 * Add the array of classes to the active parse list.
	 */
    public void addToParseList(ArrayList<Class<?>> parseList) {
        if (parseList == null)
            return;
        _parseList.addAll(parseList);
    }

    /*
     * Add the class to the active parse list.
     */
    public void addToParseList(Class<?> parentCls) {
        if (parentCls == null)
            return;
        _parseList.add(parentCls);
    }

    /*
     * Whether the active parse list contains the specified class.
     */
    public boolean parseListContains(Class<?> cls) {
        if (_parseList.size() == 0)
            return false;
        return _parseList.contains(cls);
    }

    /*
     * Returns the list of classes actively being parsed.
     */
    public ArrayList<Class<?>> getParseList() {
        return _parseList;
    }

    /*
     * Returns class currently being parsed.
     */
    public Class<?> getParseClass() {
        return _cls;
    }

    protected boolean startDelimitedIdentifiers() {
        return false;
    }
    
    protected String normalizeSequenceName(String seqName) {
        return seqName;
    }

    protected String normalizeSchemaName(String schName) {
        return schName;
    }

    protected String normalizeCatalogName(String catName) {
        return catName;
    }
}
