org.talend.mdm.commmon.metadata.MetadataRepository.java Source code

Java tutorial

Introduction

Here is the source code for org.talend.mdm.commmon.metadata.MetadataRepository.java

Source

/*
 * Copyright (C) 2006-2016 Talend Inc. - www.talend.com
 * 
 * This source code is available under agreement available at
 * %InstallDIR%\features\org.talend.rcp.branding.%PRODUCTNAME%\%PRODUCTNAME%license.txt
 * 
 * You should have received a copy of the agreement along with this program; if not, write to Talend SA 9 rue Pages
 * 92150 Suresnes, France
 */

package org.talend.mdm.commmon.metadata;

import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Stack;
import java.util.TreeMap;

import javax.xml.XMLConstants;

import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.xsd.XSDAnnotation;
import org.eclipse.xsd.XSDComplexTypeContent;
import org.eclipse.xsd.XSDComplexTypeDefinition;
import org.eclipse.xsd.XSDConstrainingFacet;
import org.eclipse.xsd.XSDDiagnostic;
import org.eclipse.xsd.XSDDiagnosticSeverity;
import org.eclipse.xsd.XSDElementDeclaration;
import org.eclipse.xsd.XSDEnumerationFacet;
import org.eclipse.xsd.XSDFractionDigitsFacet;
import org.eclipse.xsd.XSDIdentityConstraintDefinition;
import org.eclipse.xsd.XSDLengthFacet;
import org.eclipse.xsd.XSDMaxLengthFacet;
import org.eclipse.xsd.XSDModelGroup;
import org.eclipse.xsd.XSDParticle;
import org.eclipse.xsd.XSDParticleContent;
import org.eclipse.xsd.XSDSchema;
import org.eclipse.xsd.XSDSimpleTypeDefinition;
import org.eclipse.xsd.XSDTotalDigitsFacet;
import org.eclipse.xsd.XSDTypeDefinition;
import org.eclipse.xsd.XSDXPathDefinition;
import org.eclipse.xsd.util.XSDParser;
import org.talend.mdm.commmon.metadata.annotation.DefaultValueRuleProcessor;
import org.talend.mdm.commmon.metadata.annotation.DescriptionAnnotationProcessor;
import org.talend.mdm.commmon.metadata.annotation.ForeignKeyProcessor;
import org.talend.mdm.commmon.metadata.annotation.LabelAnnotationProcessor;
import org.talend.mdm.commmon.metadata.annotation.LookupFieldProcessor;
import org.talend.mdm.commmon.metadata.annotation.PrimaryKeyInfoProcessor;
import org.talend.mdm.commmon.metadata.annotation.SchematronProcessor;
import org.talend.mdm.commmon.metadata.annotation.UserAccessProcessor;
import org.talend.mdm.commmon.metadata.annotation.XmlSchemaAnnotationProcessor;
import org.talend.mdm.commmon.metadata.annotation.XmlSchemaAnnotationProcessorState;
import org.talend.mdm.commmon.metadata.validation.ValidationFactory;
import org.talend.mdm.commmon.metadata.xsd.XSDVisitor;
import org.talend.mdm.commmon.metadata.xsd.XmlSchemaWalker;
import org.talend.mdm.commmon.util.core.ICoreConstants;

/**
 *
 */
public class MetadataRepository implements MetadataVisitable, XSDVisitor, Serializable {

    private static final long serialVersionUID = 239525645052486072L;

    public static final String COMPLEX_TYPE_NAME = "metadata.complex.type.name"; //$NON-NLS-1$

    public static final String DATA_MAX_LENGTH = "metadata.data.length"; //$NON-NLS-1$

    public static final String DATA_TOTAL_DIGITS = "metadata.data.totalDigits"; //$NON-NLS-1$

    public static final String DATA_FRACTION_DIGITS = "metadata.data.fractionDigits"; //$NON-NLS-1$

    public static final String DATA_ZIPPED = "metadata.zipped"; //$NON-NLS-1$

    public static final String XSD_LINE_NUMBER = "metadata.xsd.line"; //$NON-NLS-1$

    public static final String XSD_COLUMN_NUMBER = "metadata.xsd.column"; //$NON-NLS-1$

    public static final String XSD_DOM_ELEMENT = "metadata.xsd.dom.element"; //$NON-NLS-1$

    public static final String ANONYMOUS_PREFIX = "X_ANONYMOUS"; //$NON-NLS-1$

    public static final String DEFAULT_VALUE_RULE = "default.value.rule"; //$NON-NLS-1$

    public static final String FN_TRUE = "fn:true()"; //$NON-NLS-1$

    public static final String FN_FALSE = "fn:false()"; //$NON-NLS-1$

    private static final Logger LOGGER = Logger.getLogger(MetadataRepository.class);

    private final Map<XSDTypeDefinition, List<ComplexTypeMetadata>> entityTypeUsage = new HashMap<XSDTypeDefinition, List<ComplexTypeMetadata>>() {

        @Override
        public List<ComplexTypeMetadata> get(Object key) {
            List<ComplexTypeMetadata> types = super.get(key);
            if (types == null) {
                types = new LinkedList<ComplexTypeMetadata>();
                super.put((XSDTypeDefinition) key, types);
            }
            return types;
        }
    };

    private final static List<XmlSchemaAnnotationProcessor> XML_ANNOTATIONS_PROCESSORS = Arrays.asList(
            new ForeignKeyProcessor(), new UserAccessProcessor(), new SchematronProcessor(),
            new PrimaryKeyInfoProcessor(), new LookupFieldProcessor(), new LabelAnnotationProcessor(),
            new DescriptionAnnotationProcessor(), new DefaultValueRuleProcessor());

    private final static String USER_NAMESPACE = StringUtils.EMPTY;

    // Keep a version of types that doesn't change from one model to another
    private final static MetadataRepository commonTypes = new MetadataRepository();

    private final Map<String, Map<String, TypeMetadata>> entityTypes = new HashMap<String, Map<String, TypeMetadata>>();

    private final Map<String, Map<String, TypeMetadata>> nonInstantiableTypes = new HashMap<String, Map<String, TypeMetadata>>();

    private final Stack<ComplexTypeMetadata> currentTypeStack = new Stack<ComplexTypeMetadata>();

    private String targetNamespace;

    private int anonymousCounter = 0;

    static {
        // Load XML Schema types
        InputStream xmlSchemaDef = MetadataRepository.class.getResourceAsStream("XMLSchema.xsd"); //$NON-NLS-1$
        if (xmlSchemaDef == null) {
            throw new IllegalStateException("Could not find XML schema definition.");
        }
        commonTypes.load(xmlSchemaDef, NoOpValidationHandler.INSTANCE);
        // TMDM-4444: Adds standard Talend types such as UUID.
        InputStream internalTypes = MetadataRepository.class.getResourceAsStream("talend_types.xsd"); //$NON-NLS-1$
        if (internalTypes == null) {
            throw new IllegalStateException("Could not find internal type data model.");
        }
        commonTypes.load(internalTypes, NoOpValidationHandler.INSTANCE);
        // Prevent further modifications on common types
        for (Map.Entry<String, Map<String, TypeMetadata>> entry : commonTypes.nonInstantiableTypes.entrySet()) {
            commonTypes.nonInstantiableTypes.put(entry.getKey(), Collections.unmodifiableMap(entry.getValue()));
        }
        for (Map.Entry<String, Map<String, TypeMetadata>> entry : commonTypes.entityTypes.entrySet()) {
            if (entry.getValue() != null) {
                commonTypes.entityTypes.put(entry.getKey(), Collections.unmodifiableMap(entry.getValue()));
            }
        }
    }

    public MetadataRepository() {
        if (commonTypes != null) {
            for (Map.Entry<String, Map<String, TypeMetadata>> entry : commonTypes.nonInstantiableTypes.entrySet()) {
                this.nonInstantiableTypes.put(entry.getKey(), new TreeMap<String, TypeMetadata>(entry.getValue()));
            }
            for (Map.Entry<String, Map<String, TypeMetadata>> entry : commonTypes.entityTypes.entrySet()) {
                if (entry.getValue() != null) {
                    this.entityTypes.put(entry.getKey(), new TreeMap<String, TypeMetadata>(entry.getValue()));
                }
            }
        }
    }

    public TypeMetadata getType(String name) {
        return getType(USER_NAMESPACE, name);
    }

    public String getUserNamespace() {
        return USER_NAMESPACE;
    }

    public ComplexTypeMetadata getComplexType(String typeName) {
        if (typeName == null) {
            throw new IllegalArgumentException("Type cannot be null.");
        }
        try {
            return (ComplexTypeMetadata) getType(USER_NAMESPACE, typeName.trim());
        } catch (ClassCastException e) {
            throw new IllegalArgumentException("Type named '" + typeName + "' is not a complex type.");
        }
    }

    public TypeMetadata getType(String nameSpace, String name) {
        if (nameSpace == null) {
            nameSpace = StringUtils.EMPTY;
        }
        Map<String, TypeMetadata> nameSpaceTypes = entityTypes.get(nameSpace);
        if (nameSpaceTypes == null) {
            return null;
        }
        return nameSpaceTypes.get(name.trim());
    }

    /**
     * @return Returns only {@link ComplexTypeMetadata} types defined in the data model by the MDM user (no types
     * potentially defined in other name spaces such as the XML schema's one).
     */
    public Collection<ComplexTypeMetadata> getUserComplexTypes() {
        List<ComplexTypeMetadata> complexTypes = new LinkedList<ComplexTypeMetadata>();
        // User types are all located in the default (empty) name space.
        Map<String, TypeMetadata> userNamespace = entityTypes.get(USER_NAMESPACE);
        if (userNamespace == null) {
            return Collections.emptyList();
        }
        Collection<TypeMetadata> namespaceTypes = userNamespace.values();
        for (TypeMetadata namespaceType : namespaceTypes) {
            if (namespaceType instanceof ComplexTypeMetadata) {
                complexTypes.add((ComplexTypeMetadata) namespaceType);
            }
        }
        return complexTypes;
    }

    public Collection<TypeMetadata> getTypes() {
        List<TypeMetadata> allTypes = new LinkedList<TypeMetadata>();
        Collection<Map<String, TypeMetadata>> nameSpaces = entityTypes.values();
        for (Map<String, TypeMetadata> nameSpace : nameSpaces) {
            allTypes.addAll(nameSpace.values());
        }
        nameSpaces = nonInstantiableTypes.values();
        for (Map<String, TypeMetadata> nameSpace : nameSpaces) {
            allTypes.addAll(nameSpace.values());
        }
        return allTypes;
    }

    public TypeMetadata getNonInstantiableType(String namespace, String typeName) {
        if (namespace == null) {
            namespace = StringUtils.EMPTY;
        }
        Map<String, TypeMetadata> map = nonInstantiableTypes.get(namespace);
        if (map != null) {
            return map.get(typeName.trim());
        }
        return null;
    }

    public List<ComplexTypeMetadata> getNonInstantiableTypes() {
        Map<String, TypeMetadata> map = nonInstantiableTypes.get(USER_NAMESPACE);
        List<ComplexTypeMetadata> nonInstantiableTypes = new LinkedList<ComplexTypeMetadata>();
        if (map != null) {
            for (TypeMetadata typeMetadata : map.values()) {
                if (typeMetadata instanceof ComplexTypeMetadata) {
                    nonInstantiableTypes.add((ComplexTypeMetadata) typeMetadata);
                }
            }
        }
        return nonInstantiableTypes;
    }

    public void load(InputStream inputStream) {
        load(inputStream, getValidationHandler());
    }

    protected ValidationHandler getValidationHandler() {
        return new DefaultValidationHandler();
    }

    public void load(InputStream inputStream, ValidationHandler handler) {
        if (inputStream == null) {
            throw new IllegalArgumentException("Input stream can not be null.");
        }
        // Validates data model using shared studio / server classes
        // Load user defined data model now
        Map<String, Object> options = new HashMap<String, Object>();
        options.put(Resource.OPTION_SAVE_ONLY_IF_CHANGED, Resource.OPTION_SAVE_ONLY_IF_CHANGED_MEMORY_BUFFER);
        XSDParser parse = new XSDParser(options);
        parse.parse(inputStream);
        XSDSchema schema = parse.getSchema();
        if (schema == null) {
            throw new IllegalStateException(
                    "No schema parsed from input (make sure stream contains a data model).");
        }
        schema.validate();
        EList<XSDDiagnostic> diagnostics = schema.getDiagnostics();
        for (XSDDiagnostic diagnostic : diagnostics) {
            XSDDiagnosticSeverity severity = diagnostic.getSeverity();
            if (XSDDiagnosticSeverity.ERROR_LITERAL.equals(severity)) {
                handler.error((TypeMetadata) null, "XSD validation error: " + diagnostic.getMessage(), null, -1, -1,
                        ValidationError.XML_SCHEMA);
            } else if (XSDDiagnosticSeverity.WARNING_LITERAL.equals(severity)) {
                handler.error((TypeMetadata) null, "XSD validation warning: " + diagnostic.getMessage(), null, -1,
                        -1, ValidationError.XML_SCHEMA);
            }
        }
        XmlSchemaWalker.walk(schema, this);
        // TMDM-4876 Additional processing for entity inheritance
        resolveAdditionalSuperTypes(this);
        // "Freeze" all types (ensure all soft references now point to actual types in the repository).
        nonInstantiableTypes.put(getUserNamespace(), freezeTypes(nonInstantiableTypes.get(getUserNamespace())));
        // "Freeze" all reusable type usages in the data model.
        freezeUsages();
        entityTypes.put(getUserNamespace(), freezeTypes(entityTypes.get(getUserNamespace())));
        // Validate types
        for (TypeMetadata type : getUserComplexTypes()) {
            if (!XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(type.getNamespace())) {
                type.validate(handler);
            }
        }
        for (TypeMetadata type : getNonInstantiableTypes()) {
            if (!XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(type.getNamespace())) {
                type.validate(handler);
            }
        }
        ValidationFactory.getRule(this).perform(handler); // Perform data model-scoped validation (e.g. cycles).
        handler.end();
        if (handler.getErrorCount() != 0) {
            LOGGER.error("Could not parse data model (" + handler.getErrorCount() + " error(s) found).");
        }
    }

    protected void freezeUsages() {
        for (List<ComplexTypeMetadata> entityTypes : entityTypeUsage.values()) {
            for (ComplexTypeMetadata entityType : entityTypes) {
                entityType.accept(new DefaultMetadataVisitor<Void>() {
                    @Override
                    public Void visit(ContainedComplexTypeMetadata containedType) {
                        containedType.getContainedType().declareUsage(containedType);
                        containedType.finalizeUsage();
                        for (ComplexTypeMetadata subType : containedType.getSubTypes()) {
                            if (!isCircle(containedType, subType)) {
                                subType.accept(this);
                            }
                        }
                        if (isCircle(containedType, null)) {
                            return null;
                        }
                        return super.visit(containedType);
                    }
                });
            }
        }
    }

    public static boolean isCircle(ComplexTypeMetadata containedType, ComplexTypeMetadata subType) {
        if (subType == null) {
            subType = containedType;
        }
        if (containedType != null && containedType.getContainer() != null) {
            TypeMetadata parentType = containedType.getContainer().getContainingType();
            while (parentType instanceof ContainedComplexTypeMetadata) {
                if (parentType.getName().equals(subType.getName())) {
                    return true;
                } else {
                    parentType = ((ContainedComplexTypeMetadata) parentType).getContainer().getContainingType();
                }
            }
        }
        return false;
    }

    private Map<String, TypeMetadata> freezeTypes(Map<String, TypeMetadata> typesToFreeze) {
        if (typesToFreeze == null) {
            return null;
        }
        Map<String, TypeMetadata> workingTypes = new TreeMap<String, TypeMetadata>(typesToFreeze);
        for (TypeMetadata type : typesToFreeze.values()) {
            workingTypes.put(type.getName(), type.freeze());
        }
        return workingTypes;
    }

    private static void resolveAdditionalSuperTypes(MetadataRepository repository) {
        Collection<ComplexTypeMetadata> types = repository.getUserComplexTypes();
        for (TypeMetadata current : types) {
            String complexTypeName = current.getData(COMPLEX_TYPE_NAME);
            if (complexTypeName != null) {
                TypeMetadata nonInstantiableType = repository.getNonInstantiableType(USER_NAMESPACE,
                        complexTypeName);
                if (!nonInstantiableType.getSuperTypes().isEmpty() && !nonInstantiableType.isFrozen()) {
                    TypeMetadata superType = nonInstantiableType.getSuperTypes().iterator().next();
                    ComplexTypeMetadata entitySuperType = null;
                    int entitySuperTypeCandidateCount = 0;
                    for (TypeMetadata entity : types) {
                        if (superType.getName().equals(entity.getData(COMPLEX_TYPE_NAME))) {
                            entitySuperType = (ComplexTypeMetadata) entity;
                            entitySuperTypeCandidateCount++;
                        }
                    }
                    if (entitySuperTypeCandidateCount > 1) {
                        // TMDM-7583: Found multiple entity types as candidate for entity super types, consider type will only
                        // inherit from reusable and won't have entity super type.
                        LOGGER.warn("Type '" + current.getName()
                                + "' uses multiple inheritance (following reusable type usages), consider inheritance from '"
                                + superType.getName() + "' as a reuse.");
                    } else if (entitySuperType != null) {
                        current.addSuperType(entitySuperType);
                    }
                }
            }
        }
    }

    @Override
    public <T> T accept(MetadataVisitor<T> visitor) {
        return visitor.visit(this);
    }

    public void addTypeMetadata(TypeMetadata typeMetadata) {
        String namespace = typeMetadata.getNamespace();
        if (typeMetadata.isInstantiable()) {
            registerType(typeMetadata, namespace, entityTypes);
        } else {
            registerType(typeMetadata, namespace, nonInstantiableTypes);
        }
    }

    private static void registerType(TypeMetadata typeMetadata, String namespace,
            Map<String, Map<String, TypeMetadata>> typeMap) {
        if (namespace == null) {
            namespace = StringUtils.EMPTY;
        }
        Map<String, TypeMetadata> nameSpace = typeMap.get(namespace);
        if (nameSpace == null) {
            nameSpace = new TreeMap<String, TypeMetadata>();
            typeMap.put(namespace, nameSpace);
        }
        typeMap.get(namespace).put(typeMetadata.getName(), typeMetadata);
    }

    public void close() {
        entityTypes.clear();
        nonInstantiableTypes.clear();
    }

    public Collection<TypeMetadata> getInstantiableTypes() {
        return entityTypes.get(USER_NAMESPACE).values();
    }

    @Override
    public void visitSchema(XSDSchema xmlSchema) {
        targetNamespace = xmlSchema.getTargetNamespace() == null ? USER_NAMESPACE : xmlSchema.getTargetNamespace();
        if (!currentTypeStack.isEmpty()) {
            if (LOGGER.isDebugEnabled()) {
                StringBuilder builder = new StringBuilder();
                for (ComplexTypeMetadata unprocessedType : currentTypeStack) {
                    builder.append(unprocessedType.getName()).append(" "); //$NON-NLS-1$
                }
                LOGGER.debug("Unprocessed types: " + builder);
            }
            // At the end of data model parsing, we expect all entity types to be processed.
            throw new IllegalStateException(currentTypeStack.size() + " types have not been correctly parsed.");
        }
    }

    @Override
    public void visitSimpleType(XSDSimpleTypeDefinition type) {
        String typeName = type.getName();
        TypeMetadata typeMetadata = getNonInstantiableType(targetNamespace, typeName);
        if (typeMetadata == null) {
            typeMetadata = new SimpleTypeMetadata(targetNamespace, typeName);
        }
        List<TypeMetadata> superTypes = new LinkedList<TypeMetadata>();
        if (typeName == null) {
            // Anonymous simple type (expects this is a restriction of a simple type or fails).
            XSDSimpleTypeDefinition baseTypeDefinition = type.getBaseTypeDefinition();
            if (baseTypeDefinition != null) {
                typeName = baseTypeDefinition.getName();
            } else {
                throw new NotImplementedException("Support for " + type);
            }
        } else {
            // Simple type might inherit from other simple types (i.e. UUID from string).
            XSDSimpleTypeDefinition baseType = type.getBaseTypeDefinition();
            if (baseType != null && baseType.getName() != null) {
                superTypes.add(new SoftTypeRef(this, baseType.getTargetNamespace(), baseType.getName(), false));
                EList<XSDConstrainingFacet> facets = type.getFacetContents();
                for (XSDConstrainingFacet currentFacet : facets) {
                    if (currentFacet instanceof XSDMaxLengthFacet) {
                        typeMetadata.setData(MetadataRepository.DATA_MAX_LENGTH,
                                String.valueOf(((XSDMaxLengthFacet) currentFacet).getValue()));
                    } else if (currentFacet instanceof XSDLengthFacet) {
                        typeMetadata.setData(MetadataRepository.DATA_MAX_LENGTH,
                                String.valueOf(((XSDLengthFacet) currentFacet).getValue()));
                    } else if (currentFacet instanceof XSDTotalDigitsFacet) {//this is the totalDigits
                        typeMetadata.setData(MetadataRepository.DATA_TOTAL_DIGITS,
                                String.valueOf(((XSDTotalDigitsFacet) currentFacet).getValue()));
                    } else if (currentFacet instanceof XSDFractionDigitsFacet) { // this is the fractionDigits
                        typeMetadata.setData(MetadataRepository.DATA_FRACTION_DIGITS,
                                String.valueOf(((XSDFractionDigitsFacet) currentFacet).getValue()));
                    } else if (LOGGER.isTraceEnabled()) {
                        LOGGER.trace("Ignore simple type facet on type '" + typeName + "': " + currentFacet);
                    }
                }
            }
        }
        if (getNonInstantiableType(targetNamespace, typeName) == null) {
            for (TypeMetadata superType : superTypes) {
                typeMetadata.addSuperType(superType);
            }
            addTypeMetadata(typeMetadata);
        }
    }

    @Override
    public void visitComplexType(XSDComplexTypeDefinition type) {
        String typeName = type.getName();
        boolean isNonInstantiableType = currentTypeStack.isEmpty();
        if (isNonInstantiableType) {
            if (nonInstantiableTypes.get(getUserNamespace()) != null) {
                if (nonInstantiableTypes.get(getUserNamespace()).containsKey(typeName)) {
                    // Ignore another definition of type (already processed).
                    return;
                }
            }
            // There's no current 'entity' type being parsed, this is a complex type not to be used for entity but
            // might be referenced by others entities (for fields, inheritance...).
            ComplexTypeMetadata nonInstantiableType = new ComplexTypeMetadataImpl(targetNamespace, typeName, false,
                    type.isAbstract());
            // Keep line and column of definition
            nonInstantiableType.setData(XSD_LINE_NUMBER, XSDParser.getStartLine(type.getElement()));
            nonInstantiableType.setData(XSD_COLUMN_NUMBER, XSDParser.getStartColumn(type.getElement()));
            nonInstantiableType.setData(XSD_DOM_ELEMENT, type.getElement());
            addTypeMetadata(nonInstantiableType);
            currentTypeStack.push(nonInstantiableType);
            // If type is used, declare usage
            List<ComplexTypeMetadata> usages = entityTypeUsage.get(type);
            for (ComplexTypeMetadata usage : usages) {
                nonInstantiableType.declareUsage(usage);
            }
        } else {
            // Keep track of the complex type used for entity type (especially for inheritance).
            if (typeName != null) {
                currentTypeStack.peek().setData(MetadataRepository.COMPLEX_TYPE_NAME, typeName);
            }
        }
        XSDComplexTypeContent particle = type.getContent();
        if (particle instanceof XSDParticle) {
            XSDParticle currentParticle = (XSDParticle) particle;
            if (currentParticle.getTerm() instanceof XSDModelGroup) {
                XSDModelGroup group = (XSDModelGroup) currentParticle.getTerm();
                EList<XSDParticle> particles = group.getContents();
                for (XSDParticle p : particles) {
                    XSDParticleContent particleContent = p.getContent();
                    XmlSchemaWalker.walk(particleContent, this);
                }
            }
        } else if (particle != null) {
            throw new IllegalArgumentException(
                    "Not supported XML Schema particle: " + particle.getClass().getName());
        }
        // Adds the type information about super types.
        XSDTypeDefinition contentModel = type.getBaseTypeDefinition();
        if (contentModel != null) {
            if (!XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(contentModel.getTargetNamespace())
                    && !Types.ANY_TYPE.equals(contentModel.getName())) {
                SoftTypeRef superType = new SoftTypeRef(this, contentModel.getTargetNamespace(),
                        contentModel.getName(), false);
                if (currentTypeStack.peek() instanceof ContainedComplexTypeMetadata) {
                    superType.declareUsage(currentTypeStack.peek());
                }
                currentTypeStack.peek().addSuperType(superType);
                particle = type.getContent();
                if (particle instanceof XSDParticle) {
                    XSDParticle currentParticle = (XSDParticle) particle;
                    if (currentParticle.getTerm() instanceof XSDModelGroup) {
                        XSDModelGroup group = (XSDModelGroup) currentParticle.getTerm();
                        EList<XSDParticle> particles = group.getContents();
                        for (XSDParticle p : particles) {
                            XSDParticleContent particleContent = p.getContent();
                            XmlSchemaWalker.walk(particleContent, this);
                        }
                    }
                } else if (particle != null) {
                    throw new IllegalArgumentException(
                            "Not supported XML Schema particle: " + particle.getClass().getName());
                }
            }
        }
        if (isNonInstantiableType) {
            currentTypeStack.pop();
        }
    }

    @Override
    public void visitElement(XSDElementDeclaration element) {
        if (currentTypeStack.isEmpty()) { // "top level" elements means new MDM entity type
            String typeName = element.getName();
            if (getComplexType(typeName) != null) { // Don't process twice type
                return;
            }
            // If entity's type is abstract
            boolean isAbstract = false;
            XSDTypeDefinition typeDefinition = element.getTypeDefinition();
            if (typeDefinition != null && typeDefinition instanceof XSDComplexTypeDefinition) {
                isAbstract = ((XSDComplexTypeDefinition) typeDefinition).isAbstract();
            }
            // Id fields
            Map<String, XSDXPathDefinition> idFields = new LinkedHashMap<String, XSDXPathDefinition>();
            EList<XSDIdentityConstraintDefinition> constraints = element.getIdentityConstraintDefinitions();
            for (XSDIdentityConstraintDefinition constraint : constraints) {
                EList<XSDXPathDefinition> fields = constraint.getFields();
                for (XSDXPathDefinition field : fields) {
                    idFields.put(field.getValue(), field);
                }
            }
            XmlSchemaAnnotationProcessorState state;
            try {
                XSDAnnotation annotation = element.getAnnotation();
                state = new XmlSchemaAnnotationProcessorState();
                for (XmlSchemaAnnotationProcessor processor : XML_ANNOTATIONS_PROCESSORS) {
                    processor.process(this, null, annotation, state);
                }
            } catch (Exception e) {
                throw new RuntimeException(
                        "Annotation processing exception while parsing info for type '" + typeName + "'.", e);
            }
            // If write is not allowed for everyone, at least add "administration".
            if (state.getAllowWrite().isEmpty()) {
                state.getAllowWrite().add(ICoreConstants.ADMIN_PERMISSION);
            }
            ComplexTypeMetadata type = new ComplexTypeMetadataImpl(targetNamespace, typeName, state.getAllowWrite(),
                    state.getDenyCreate(), state.getHide(), state.getDenyPhysicalDelete(),
                    state.getDenyLogicalDelete(), state.getSchematron(), state.getPrimaryKeyInfo(),
                    state.getLookupFields(), true, isAbstract, state.getWorkflowAccessRights());
            // Register parsed localized labels
            Map<Locale, String> localeToLabel = state.getLocaleToLabel();
            for (Map.Entry<Locale, String> entry : localeToLabel.entrySet()) {
                type.registerName(entry.getKey(), entry.getValue());
            }
            // Register parsed localized descriptions
            Map<Locale, String> localeToDescription = state.getLocaleToDescription();
            for (Map.Entry<Locale, String> entry : localeToDescription.entrySet()) {
                type.registerDescription(entry.getKey(), entry.getValue());
            }
            // Keep line and column of definition
            type.setData(XSD_LINE_NUMBER, XSDParser.getStartLine(element.getElement()));
            type.setData(XSD_COLUMN_NUMBER, XSDParser.getStartColumn(element.getElement()));
            type.setData(XSD_DOM_ELEMENT, element.getElement());
            addTypeMetadata(type);
            // Keep usage information
            entityTypeUsage.get(element.getType()).add(type);
            // Walk the fields
            currentTypeStack.push(type);
            {
                XmlSchemaWalker.walk(element.getType(), this);
            }
            currentTypeStack.pop();
            // Super types
            XSDElementDeclaration substitutionGroup = element.getSubstitutionGroupAffiliation();
            if (substitutionGroup != null
                    && !XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(substitutionGroup.getTargetNamespace())
                    && !Types.ANY_TYPE.equals(substitutionGroup.getName())) {
                if (!substitutionGroup.getResolvedElementDeclaration().equals(element)) {
                    SoftTypeRef superType = new SoftTypeRef(this, substitutionGroup.getTargetNamespace(),
                            substitutionGroup.getName(), true);
                    type.addSuperType(superType);
                }
            }
            // Register keys (TMDM-4470).
            for (Map.Entry<String, XSDXPathDefinition> unresolvedId : idFields.entrySet()) {
                SoftIdFieldRef keyField = new SoftIdFieldRef(this, type.getName(), unresolvedId.getKey());
                // Keep line and column of definition
                keyField.setData(XSD_LINE_NUMBER, XSDParser.getStartLine(unresolvedId.getValue().getElement()));
                keyField.setData(XSD_COLUMN_NUMBER, XSDParser.getStartColumn(unresolvedId.getValue().getElement()));
                keyField.setData(XSD_DOM_ELEMENT, unresolvedId.getValue().getElement());
                type.registerKey(keyField);
            }
            // TMDM-6264: An entity type without any key info is a element maybe referenced by others, but never an
            // entity.
            if (type.getKeyFields().isEmpty() && type.getSuperTypes().isEmpty()) {
                Map<String, TypeMetadata> userEntityTypes = entityTypes.get(getUserNamespace());
                if (userEntityTypes != null) {
                    userEntityTypes.remove(type.getName());
                }
            }
        } else { // Non "top level" elements means fields for the MDM entity type being parsed
            FieldMetadata fieldMetadata;
            int minOccurs = ((XSDParticle) element.getContainer()).getMinOccurs();
            int maxOccurs = ((XSDParticle) element.getContainer()).getMaxOccurs();
            if (element.isElementDeclarationReference() && currentTypeStack.peek().getName()
                    .equals(element.getResolvedElementDeclaration().getName())) {
                return;
            }
            if (element.getResolvedElementDeclaration() != null
                    && element.getResolvedElementDeclaration().getTargetNamespace() == null) {
                fieldMetadata = createFieldMetadata(element.getResolvedElementDeclaration(),
                        currentTypeStack.peek(), minOccurs, maxOccurs);
            } else {
                fieldMetadata = createFieldMetadata(element, currentTypeStack.peek(), minOccurs, maxOccurs);
            }
            currentTypeStack.peek().addField(fieldMetadata);
        }
    }

    // TODO Refactor!
    private FieldMetadata createFieldMetadata(XSDElementDeclaration element, ComplexTypeMetadata containingType,
            int minOccurs, int maxOccurs) {
        String fieldName = element.getName();
        if (maxOccurs > 0 && minOccurs > maxOccurs) { // Eclipse XSD does not check this
            throw new IllegalArgumentException("Can not parse information on field '" + element.getQName()
                    + "' of type '" + containingType + "' (maxOccurs > minOccurs)");
        }
        boolean isMany = maxOccurs == -1 || maxOccurs > 1;
        XmlSchemaAnnotationProcessorState state = new XmlSchemaAnnotationProcessorState();
        try {
            XSDAnnotation annotation = element.getAnnotation();
            for (XmlSchemaAnnotationProcessor processor : XML_ANNOTATIONS_PROCESSORS) {
                processor.process(this, containingType, annotation, state);
            }
        } catch (Exception e) {
            throw new RuntimeException("Annotation processing exception while parsing info for field '" + fieldName
                    + "' in type '" + containingType.getName() + "'", e);
        }
        boolean isMandatory = minOccurs > 0;
        boolean isContained = false;
        boolean isReference = state.isReference();
        boolean fkIntegrity = state.isFkIntegrity();
        boolean fkIntegrityOverride = state.isFkIntegrityOverride();
        List<FieldMetadata> foreignKeyInfo = state.getForeignKeyInfo();
        String foreignKeyInfoFormat = state.getForeignKeyInfoFormat();
        TypeMetadata fieldType = state.getFieldType();
        FieldMetadata referencedField = state.getReferencedField();
        TypeMetadata referencedType = state.getReferencedType();
        List<String> hideUsers = state.getHide();
        List<String> allowWriteUsers = state.getAllowWrite();
        List<String> workflowAccessRights = state.getWorkflowAccessRights();
        String visibilityRule = state.getVisibilityRule();
        XSDTypeDefinition schemaType = element.getType();
        if (schemaType instanceof XSDSimpleTypeDefinition) {
            XSDSimpleTypeDefinition simpleSchemaType = (XSDSimpleTypeDefinition) schemaType;
            XSDSimpleTypeDefinition content = simpleSchemaType.getBaseTypeDefinition();
            if (schemaType.getQName() != null) {
                fieldType = new SoftTypeRef(this, schemaType.getTargetNamespace(), schemaType.getName(), false);
            } else {
                // Null QNames may happen for anonymous types extending other types.
                fieldType = new SimpleTypeMetadata(targetNamespace,
                        ANONYMOUS_PREFIX + String.valueOf(anonymousCounter++));
                if (content != null) {
                    fieldType.addSuperType(
                            new SoftTypeRef(this, content.getTargetNamespace(), content.getName(), false));
                }
                EList<XSDConstrainingFacet> facets = simpleSchemaType.getFacetContents();
                for (XSDConstrainingFacet currentFacet : facets) {
                    if (currentFacet instanceof XSDMaxLengthFacet) {
                        fieldType.setData(MetadataRepository.DATA_MAX_LENGTH,
                                String.valueOf(((XSDMaxLengthFacet) currentFacet).getValue()));
                    } else if (currentFacet instanceof XSDTotalDigitsFacet) {//this is the totalDigits
                        fieldType.setData(MetadataRepository.DATA_TOTAL_DIGITS,
                                String.valueOf(((XSDTotalDigitsFacet) currentFacet).getValue()));
                    } else if (currentFacet instanceof XSDFractionDigitsFacet) { // this is the fractionDigits
                        fieldType.setData(MetadataRepository.DATA_FRACTION_DIGITS,
                                String.valueOf(((XSDFractionDigitsFacet) currentFacet).getValue()));
                    } else if (LOGGER.isTraceEnabled()) {
                        LOGGER.trace("Ignore simple type facet on type '" + fieldName + "': " + currentFacet);
                    }
                }
            }
            fieldType.setData(XSD_LINE_NUMBER, XSDParser.getStartLine(element.getElement()));
            fieldType.setData(XSD_COLUMN_NUMBER, XSDParser.getStartColumn(element.getElement()));
            fieldType.setData(XSD_DOM_ELEMENT, element.getElement());
            if (isReference) {
                ReferenceFieldMetadata referenceField = new ReferenceFieldMetadata(containingType, false, isMany,
                        isMandatory, fieldName, (ComplexTypeMetadata) referencedType, referencedField,
                        foreignKeyInfo, foreignKeyInfoFormat, fkIntegrity, fkIntegrityOverride, fieldType,
                        allowWriteUsers, hideUsers, workflowAccessRights, state.getForeignKeyFilter(),
                        visibilityRule);
                referenceField.setData(XSD_LINE_NUMBER, XSDParser.getStartLine(element.getElement()));
                referenceField.setData(XSD_COLUMN_NUMBER, XSDParser.getStartColumn(element.getElement()));
                referenceField.setData(XSD_DOM_ELEMENT, element.getElement());
                setLocalizedNames(referenceField, state.getLocaleToLabel());
                setLocalizedDescriptions(referenceField, state.getLocaleToDescription());
                setDefaultValueRule(referenceField, state.getDefaultValueRule());
                return referenceField;
            }
            if (content != null) {
                if (content.getFacets().size() > 0) {
                    boolean isEnumeration = false;
                    for (int i = 0; i < content.getFacets().size(); i++) {
                        XSDConstrainingFacet item = content.getFacets().get(i);
                        if (item instanceof XSDEnumerationFacet) {
                            isEnumeration = true;
                        }
                    }
                    if (isEnumeration) {
                        EnumerationFieldMetadata enumField = new EnumerationFieldMetadata(containingType, false,
                                isMany, isMandatory, fieldName, fieldType, allowWriteUsers, hideUsers,
                                workflowAccessRights, visibilityRule);
                        enumField.setData(XSD_LINE_NUMBER, XSDParser.getStartLine(element.getElement()));
                        enumField.setData(XSD_COLUMN_NUMBER, XSDParser.getStartColumn(element.getElement()));
                        enumField.setData(XSD_DOM_ELEMENT, element.getElement());
                        setLocalizedNames(enumField, state.getLocaleToLabel());
                        setLocalizedDescriptions(enumField, state.getLocaleToDescription());
                        setDefaultValueRule(enumField, state.getDefaultValueRule());
                        return enumField;
                    } else {
                        FieldMetadata field = new SimpleTypeFieldMetadata(containingType, false, isMany,
                                isMandatory, fieldName, fieldType, allowWriteUsers, hideUsers, workflowAccessRights,
                                visibilityRule);
                        field.setData(XSD_LINE_NUMBER, XSDParser.getStartLine(element.getElement()));
                        field.setData(XSD_COLUMN_NUMBER, XSDParser.getStartColumn(element.getElement()));
                        field.setData(XSD_DOM_ELEMENT, element.getElement());
                        setLocalizedNames(field, state.getLocaleToLabel());
                        setLocalizedDescriptions(field, state.getLocaleToDescription());
                        setDefaultValueRule(field, state.getDefaultValueRule());
                        return field;
                    }
                } else {
                    FieldMetadata field = new SimpleTypeFieldMetadata(containingType, false, isMany, isMandatory,
                            fieldName, fieldType, allowWriteUsers, hideUsers, workflowAccessRights, visibilityRule);
                    field.setData(XSD_LINE_NUMBER, XSDParser.getStartLine(element.getElement()));
                    field.setData(XSD_COLUMN_NUMBER, XSDParser.getStartColumn(element.getElement()));
                    field.setData(XSD_DOM_ELEMENT, element.getElement());
                    setLocalizedNames(field, state.getLocaleToLabel());
                    setLocalizedDescriptions(field, state.getLocaleToDescription());
                    setDefaultValueRule(field, state.getDefaultValueRule());
                    return field;
                }
            }
        }
        if (fieldType == null) {
            String qName = element.getType() == null ? null : element.getType().getQName();
            if (qName != null) {
                TypeMetadata metadata = getType(element.getType().getTargetNamespace(),
                        element.getType().getName());
                if (metadata != null) {
                    fieldType = new SoftTypeRef(this, targetNamespace, schemaType.getName(), false);
                    isContained = true;
                } else {
                    if (schemaType instanceof XSDComplexTypeDefinition) {
                        fieldType = new SoftTypeRef(this, schemaType.getTargetNamespace(), schemaType.getName(),
                                false);
                        isContained = true;
                    } else {
                        throw new NotImplementedException("Support for '" + schemaType.getClass() + "'.");
                    }
                }
            } else { // Ref & anonymous complex type
                isContained = true;
                XSDElementDeclaration refName = element.getResolvedElementDeclaration();
                if (schemaType != null) {
                    fieldType = new ComplexTypeMetadataImpl(targetNamespace,
                            ANONYMOUS_PREFIX + String.valueOf(anonymousCounter++), false);
                    isContained = true;
                } else if (refName != null) {
                    // Reference being an element, consider references as references to entity type.
                    fieldType = new SoftTypeRef(this, refName.getTargetNamespace(), refName.getName(), true);
                } else {
                    throw new NotImplementedException();
                }
            }
        }
        if (isContained) {
            ContainedTypeFieldMetadata containedField = new ContainedTypeFieldMetadata(containingType, isMany,
                    isMandatory, fieldName, (ComplexTypeMetadata) fieldType, allowWriteUsers, hideUsers,
                    workflowAccessRights, visibilityRule);
            containedField.setData(XSD_LINE_NUMBER, XSDParser.getStartLine(element.getElement()));
            containedField.setData(XSD_COLUMN_NUMBER, XSDParser.getStartColumn(element.getElement()));
            containedField.setData(XSD_DOM_ELEMENT, element.getElement());
            if (fieldType.getName().startsWith(ANONYMOUS_PREFIX)) {
                currentTypeStack.push((ComplexTypeMetadata) containedField.getType());
                {
                    XmlSchemaWalker.walk(schemaType, this);
                }
                currentTypeStack.pop();
            }
            setLocalizedNames(containedField, state.getLocaleToLabel());
            setLocalizedDescriptions(containedField, state.getLocaleToDescription());
            return containedField;
        } else {
            FieldMetadata field = new SimpleTypeFieldMetadata(containingType, false, isMany, isMandatory, fieldName,
                    fieldType, allowWriteUsers, hideUsers, workflowAccessRights, visibilityRule);
            field.setData(XSD_LINE_NUMBER, XSDParser.getStartLine(element.getElement()));
            field.setData(XSD_COLUMN_NUMBER, XSDParser.getStartColumn(element.getElement()));
            field.setData(XSD_DOM_ELEMENT, element.getElement());
            setLocalizedNames(field, state.getLocaleToLabel());
            setLocalizedDescriptions(field, state.getLocaleToDescription());
            return field;
        }
    }

    private static void setLocalizedNames(FieldMetadata field, Map<Locale, String> labels) {
        for (Map.Entry<Locale, String> entry : labels.entrySet()) {
            field.registerName(entry.getKey(), entry.getValue());
        }
    }

    private static void setLocalizedDescriptions(FieldMetadata field, Map<Locale, String> descriptions) {
        for (Map.Entry<Locale, String> entry : descriptions.entrySet()) {
            field.registerDescription(entry.getKey(), entry.getValue());
        }
    }

    private static void setDefaultValueRule(FieldMetadata field, String defaultValueRule) {
        if (StringUtils.isNotBlank(defaultValueRule)) {
            field.setData(DEFAULT_VALUE_RULE, defaultValueRule);
        }
    }

    public MetadataRepository copy() {
        MetadataRepository repositoryCopy = new MetadataRepository();
        // Copy first non instantiable types...
        for (Map.Entry<String, Map<String, TypeMetadata>> currentNamespace : nonInstantiableTypes.entrySet()) {
            Map<String, TypeMetadata> namespaceCopy = new HashMap<String, TypeMetadata>();
            Map<String, TypeMetadata> namespaceTypes = currentNamespace.getValue();
            if (namespaceTypes != null) {
                for (Map.Entry<String, TypeMetadata> currentType : namespaceTypes.entrySet()) {
                    namespaceCopy.put(currentType.getKey(), currentType.getValue().copy());
                }
                repositoryCopy.nonInstantiableTypes.put(currentNamespace.getKey(), namespaceCopy);
            }
        }
        // ... then copy entity types.
        for (Map.Entry<String, Map<String, TypeMetadata>> currentNamespace : entityTypes.entrySet()) {
            Map<String, TypeMetadata> namespaceCopy = new HashMap<String, TypeMetadata>();
            Map<String, TypeMetadata> namespaceTypes = currentNamespace.getValue();
            if (namespaceTypes != null) {
                for (Map.Entry<String, TypeMetadata> currentType : namespaceTypes.entrySet()) {
                    namespaceCopy.put(currentType.getKey(), currentType.getValue().copy());
                }
                repositoryCopy.entityTypes.put(currentNamespace.getKey(), namespaceCopy);
            }
        }
        return repositoryCopy;
    }

}