/*******************************************************************************
 * 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.olingo.odata2.client.core.ep.deserializer;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import org.apache.olingo.odata2.api.edm.Edm;
import org.apache.olingo.odata2.api.edm.EdmAction;
import org.apache.olingo.odata2.api.edm.EdmAnnotationAttribute;
import org.apache.olingo.odata2.api.edm.EdmAnnotationElement;
import org.apache.olingo.odata2.api.edm.EdmAnnotations;
import org.apache.olingo.odata2.api.edm.EdmAssociation;
import org.apache.olingo.odata2.api.edm.EdmAssociationEnd;
import org.apache.olingo.odata2.api.edm.EdmAssociationSet;
import org.apache.olingo.odata2.api.edm.EdmAssociationSetEnd;
import org.apache.olingo.odata2.api.edm.EdmComplexType;
import org.apache.olingo.odata2.api.edm.EdmConcurrencyMode;
import org.apache.olingo.odata2.api.edm.EdmContentKind;
import org.apache.olingo.odata2.api.edm.EdmCustomizableFeedMappings;
import org.apache.olingo.odata2.api.edm.EdmEntityContainer;
import org.apache.olingo.odata2.api.edm.EdmEntitySet;
import org.apache.olingo.odata2.api.edm.EdmEntityType;
import org.apache.olingo.odata2.api.edm.EdmException;
import org.apache.olingo.odata2.api.edm.EdmFacets;
import org.apache.olingo.odata2.api.edm.EdmFunctionImport;
import org.apache.olingo.odata2.api.edm.EdmMultiplicity;
import org.apache.olingo.odata2.api.edm.EdmNavigationProperty;
import org.apache.olingo.odata2.api.edm.EdmParameter;
import org.apache.olingo.odata2.api.edm.EdmProperty;
import org.apache.olingo.odata2.api.edm.EdmReferentialConstraint;
import org.apache.olingo.odata2.api.edm.EdmReferentialConstraintRole;
import org.apache.olingo.odata2.api.edm.EdmSimpleType;
import org.apache.olingo.odata2.api.edm.EdmSimpleTypeKind;
import org.apache.olingo.odata2.api.edm.EdmTypeKind;
import org.apache.olingo.odata2.api.edm.EdmTyped;
import org.apache.olingo.odata2.api.edm.FullQualifiedName;
import org.apache.olingo.odata2.api.edm.provider.Facets;
import org.apache.olingo.odata2.api.ep.EntityProviderException;
import org.apache.olingo.odata2.client.api.edm.EdmDataServices;
import org.apache.olingo.odata2.client.api.edm.EdmSchema;
import org.apache.olingo.odata2.client.api.edm.EdmUsing;
import org.apache.olingo.odata2.client.core.edm.EdmMetadataAssociationEnd;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmAnnotationAttributeImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmAnnotationElementImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmAnnotationsImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmAssociationEndImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmAssociationImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmAssociationSetEndImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmAssociationSetImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmComplexPropertyImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmComplexTypeImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmCustomizableFeedMappingsImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmEntityContainerImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmEntitySetImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmEntityTypeImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmFunctionImportImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmFunctionImportParameter;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmKeyImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmNamedImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmNavigationPropertyImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmOnDeleteImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmParameterImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmPropertyImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmPropertyRefImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmReferentialConstraintImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmReferentialConstraintRoleImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmSchemaImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmSimplePropertyImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmTypedImpl;
import org.apache.olingo.odata2.client.core.edm.Impl.EdmUsingImpl;
import org.apache.olingo.odata2.core.commons.XmlHelper;
import org.apache.olingo.odata2.core.edm.EdmSimpleTypeFacadeImpl;
import org.apache.olingo.odata2.core.ep.util.XmlMetadataConstants;

/**
 * This class deserializes Metadata document
 *
 */
public class XmlMetadataDeserializer {

  private Map<String, Set<String>> inscopeMap = new HashMap<String, Set<String>>();
  private Map<String, String> aliasNamespaceMap = new HashMap<String, String>();
  private Map<String, String> xmlNamespaceMap;
  private Map<String, String> mandatoryNamespaces;
  private Map<FullQualifiedName, EdmEntityType> entityTypesMap = new HashMap<FullQualifiedName, EdmEntityType>();
  private Map<FullQualifiedName, EdmComplexType> complexTypesMap = new HashMap<FullQualifiedName, EdmComplexType>();
  private Map<FullQualifiedName, EdmProperty> complexPropertyMap = new HashMap<FullQualifiedName, EdmProperty>();
  private Map<FullQualifiedName, EdmAssociation> associationsMap = new HashMap<FullQualifiedName, EdmAssociation>();
  private Map<String, EdmAssociationSet> associationSetMap = new HashMap<String, EdmAssociationSet>();
  private Map<String, EdmAssociationSet> tempAssociationSetMap = new HashMap<String, EdmAssociationSet>();
   private Map<String, List<EdmAssociationSetEndImpl>> associationSetEndMap = 
      new HashMap<String, List<EdmAssociationSetEndImpl>>();
  private Map<FullQualifiedName, EdmEntityContainer> containerMap =
      new HashMap<FullQualifiedName, EdmEntityContainer>();
  //Map for base type relation
  private Map<FullQualifiedName, FullQualifiedName> entityBaseTypeMap = 
      new HashMap<FullQualifiedName, FullQualifiedName>();
  private Map<FullQualifiedName, FullQualifiedName> complexBaseTypeMap = 
      new HashMap<FullQualifiedName, FullQualifiedName>();
  private List<EdmFunctionImport> edmFunctionImportList = new ArrayList<EdmFunctionImport>();
  private List<EdmEntitySet> edmEntitySetList = new ArrayList<EdmEntitySet>();
  private List<EdmNavigationProperty> navProperties = new ArrayList<EdmNavigationProperty>();
  private String currentHandledStartTagName;
  private String currentNamespace;
  private String edmNamespace = Edm.NAMESPACE_EDM_2008_09;
  private Set<String> edmNamespaces;
  private EdmEntityContainer defaultEdmEntityContainer;

  /**
   * 
   * @param content
   * @param validate
   * @return EdmDataServices
   * @throws EntityProviderException
   * @throws EdmException
   */
  public EdmDataServices readMetadata(final InputStream content, final boolean validate)//NOSONAR
      throws EntityProviderException, EdmException { 
    try {
      initialize();
      EdmDataServices dataServices = new EdmDataServices();
      List<EdmSchema> schemas = new ArrayList<EdmSchema>();
      EdmImpl edm = new EdmImpl();
      XMLStreamReader reader = XmlHelper.createStreamReader(content);
      while (reader.hasNext()
          && !(reader.isEndElement() && Edm.NAMESPACE_EDMX_2007_06.equals(reader.getNamespaceURI())
              && XmlMetadataConstants.EDMX_TAG.equals(reader.getLocalName()))) {
        reader.next();
        if (reader.isStartElement()) {
          extractNamespaces(reader);
          if (XmlMetadataConstants.EDM_SCHEMA.equals(reader.getLocalName())) { //NOSONAR
            edmNamespace = reader.getNamespaceURI();
            checkEdmNamespace();
            schemas.add(readSchema(reader, edm));
          } else if (XmlMetadataConstants.EDM_DATA_SERVICES.equals(reader
              .getLocalName())) {
            dataServices.setDataServiceVersion(reader.getAttributeValue(Edm.NAMESPACE_M_2007_08, "DataServiceVersion"));
          }
        }
      }

      if (!reader.isEndElement() || !XmlMetadataConstants.EDMX_TAG.equals(reader.getLocalName())) {
        throw new EntityProviderException(EntityProviderException.MISSING_TAG
            .addContent(XmlMetadataConstants.EDMX_TAG));
      }

      setBaseTypeForEntityType();
      setBaseTypeForComplexType();
      setDetailsForEntitySet();
      setAssociationSetForNavigations();
      if (validate) {
        validate();
      }
      edm.setEdmSchemas(schemas);
      dataServices.setClientEdm(edm);
      reader.close();
      return dataServices;
    } catch (XMLStreamException e) {
      throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass()
          .getSimpleName()), e);
    }

  }

  private EdmSchema readSchema(final XMLStreamReader reader, EdmImpl edm) //NOSONAR
      throws XMLStreamException, EntityProviderException, EdmException { 
    reader.require(XMLStreamConstants.START_ELEMENT, edmNamespace, XmlMetadataConstants.EDM_SCHEMA);
    final EdmSchemaImpl schemaImpl = new EdmSchemaImpl();
    final List<EdmUsing> usings = new ArrayList<EdmUsing>();
    final List<EdmComplexType> complexTypes = new ArrayList<EdmComplexType>();
    final List<EdmEntityType> entityTypes = new ArrayList<EdmEntityType>();
    final List<EdmAssociation> associations = new ArrayList<EdmAssociation>();
    final List<EdmEntityContainer> entityContainers = new ArrayList<EdmEntityContainer>();
    final List<EdmAnnotationElement> annotationElements = new ArrayList<EdmAnnotationElement>();
    schemaImpl.setNamespace(reader.getAttributeValue(null, XmlMetadataConstants.EDM_SCHEMA_NAMESPACE));
    inscopeMap.put(schemaImpl.getNamespace(), new HashSet<String>());
    schemaImpl.setAlias(reader.getAttributeValue(null, XmlMetadataConstants.EDM_SCHEMA_ALIAS));
    schemaImpl.setAnnotationAttributes(readAnnotationAttribute(reader));
    currentNamespace = schemaImpl.getNamespace();
    while (reader.hasNext()
        && !(reader.isEndElement() && edmNamespace.equals(reader.getNamespaceURI())
            && XmlMetadataConstants.EDM_SCHEMA.equals(reader.getLocalName()))) {
      reader.next();
      if (reader.isStartElement()) {
        extractNamespaces(reader);
        currentHandledStartTagName = reader.getLocalName();
        if (XmlMetadataConstants.EDM_USING.equals(currentHandledStartTagName)) {
          usings.add(readUsing(reader, schemaImpl.getNamespace()));
        } else if (XmlMetadataConstants.EDM_ENTITY_TYPE.equals(currentHandledStartTagName)) {
          entityTypes.add(readEntityType(reader, edm));
        } else if (XmlMetadataConstants.EDM_COMPLEX_TYPE.equals(currentHandledStartTagName)) {
          complexTypes.add(readComplexType(reader, edm));
        } else if (XmlMetadataConstants.EDM_ASSOCIATION.equals(currentHandledStartTagName)) {
          associations.add(readAssociation(reader, edm));
        } else if (XmlMetadataConstants.EDM_ENTITY_CONTAINER.equals(currentHandledStartTagName)) {
          entityContainers.add(readEntityContainer(reader, (EdmImpl) edm));
        } else {
          annotationElements.add(readAnnotationElement(reader));
        }
      }
    }
    if (schemaImpl.getAlias() != null) {
      aliasNamespaceMap.put(schemaImpl.getAlias(), schemaImpl.getNamespace());
      edm.setAliasToNamespaceInfo(aliasNamespaceMap);
    }
    if (!annotationElements.isEmpty()) {
      schemaImpl.setAnnotationElements(annotationElements);
    }
    schemaImpl
        .setUsings(usings)
        .setEntityTypes(entityTypes)
        .setComplexTypes(complexTypes)
        .setAssociations(associations)
        .setEntityContainers(entityContainers);

    edm.setAliasToNamespaceInfo(aliasNamespaceMap)
        .setEdmAssociations(associationsMap)
        .setEdmComplexTypes(complexTypesMap)
        .setEdmEntityContainers(containerMap)
        .setDefaultEntityContainer(defaultEdmEntityContainer)
        .setEdmEntitySets(edmEntitySetList)
        .setEdmEntityTypes(entityTypesMap)
        .setEdmFunctionImports(edmFunctionImportList);


    return schemaImpl;
  }

  private void setAssociationSetForNavigations() throws EdmException {
    for(EdmEntitySet edmEntitySet:edmEntitySetList){
      List<String> navigations =  edmEntitySet.getEntityType().getNavigationPropertyNames();
      if(navigations!=null && !navigations.isEmpty()){
        for (EdmNavigationProperty navigationProperty : navProperties) {
          if (navigations.contains(navigationProperty.getName())) { //NOSONAR
            FullQualifiedName associationName = ((EdmNavigationPropertyImpl) navigationProperty).getRelationshipName();
            String toRoleName = ((EdmNavigationPropertyImpl) navigationProperty).getToRole();
            EdmAssociationEnd end = associationsMap.get(associationName).getEnd(toRoleName);
            if (end == null) {
              throw new EdmException(EdmException.ASSOCIATIONNOTFOUND);
            }
            String relation = associationName.toString();
            StringBuilder key = new StringBuilder();
            key.append(edmEntitySet.getName());
            key.append(">>");
            key.append(relation);
            key.append(">>");
            key.append(navigationProperty.getFromRole());
            ((EdmNavigationPropertyImpl) navigationProperty).setMultiplicity(end.getMultiplicity());
            associationSetMap.put(key.toString(), tempAssociationSetMap.get(relation));
          }
        }
      }
    }
  }

  private void setDetailsForEntitySet() throws EdmException {
    for (EdmEntitySet entitySet : edmEntitySetList) {
      EdmEntitySetImpl entitySetImpl = (EdmEntitySetImpl) entitySet;
      FullQualifiedName entityTypeKey = entitySetImpl.getEntityTypeName();
      if(aliasNamespaceMap.get(entitySetImpl.getEntityTypeName().getNamespace()) != null){
        entityTypeKey = new FullQualifiedName(aliasNamespaceMap.get(entitySetImpl.getEntityTypeName().getNamespace()),
            entitySetImpl.getEntityTypeName().getName());
      }
      if (entitySetImpl.getEntityType() == null && entityTypeKey != null) {
        if(entityTypesMap.get(entityTypeKey) != null){
          entitySetImpl.setEdmEntityType(entityTypesMap.get(entityTypeKey));
        }else{
          EdmEntityTypeImpl edmEntityType = new EdmEntityTypeImpl();
          edmEntityType.setName(entitySetImpl.getEntityTypeName().getName());
          edmEntityType.setNamespace(entitySetImpl.getEntityTypeName().getNamespace());
          entitySetImpl.setEdmEntityType(edmEntityType);
        }
      }
      if (associationSetEndMap.get(entitySet.getName())!=null) {
         List<EdmAssociationSetEndImpl> ends = associationSetEndMap.get(entitySet.getName());
         for(EdmAssociationSetEndImpl end:ends){
           end.setEntitySet(entitySet);
         }
      }
    }

  }

  private void setBaseTypeForComplexType() throws EdmException {
    for (Entry<FullQualifiedName, EdmComplexType> entity : complexTypesMap.entrySet()) {
      EdmComplexTypeImpl entityType = (EdmComplexTypeImpl) entity.getValue();
      if (((EdmComplexTypeImpl) entity.getValue()).getEdmBaseTypeName() != null && 
          entity.getValue().getBaseType() == null) {
        FullQualifiedName fqname = entityType.getEdmBaseTypeName();
        if(complexTypesMap.get(entityType.getEdmBaseTypeName()) != null){
          entityType.setEdmBaseType(complexTypesMap.get(fqname));
        }else if (aliasNamespaceMap.containsKey(fqname.getNamespace())) {
          FullQualifiedName changedName = new FullQualifiedName(
              aliasNamespaceMap.get(fqname.getNamespace()), fqname.getName());
          entityType.setEdmBaseType(complexTypesMap.get(changedName));
        }else{
          //Adding dummy basetype awhich will fail during validation
          EdmComplexTypeImpl newBaseType = new EdmComplexTypeImpl();
          newBaseType.setName(fqname.getName());
          newBaseType.setNamespace(fqname.getNamespace());
          ((EdmComplexTypeImpl) entity.getValue()).setEdmBaseType(newBaseType);
           break;
        }
        
      }
    }
    setBaseTypePropertiesForComplexType(complexBaseTypeMap);
  }

  private void setBaseTypeForEntityType() throws EdmException {
    for (Entry<FullQualifiedName, EdmEntityType> entity : entityTypesMap.entrySet()) {
      EdmEntityTypeImpl entityType = (EdmEntityTypeImpl) entity.getValue();
      if (entityType.getBaseTypeName() != null && entity.getValue()
          .getBaseType() == null) {
        FullQualifiedName fqname = entityType.getBaseTypeName();
        if (entityTypesMap.get(entityType.getBaseTypeName()) != null) {
          entityType.setEdmBaseType(entityTypesMap.get(fqname));
        } else if (aliasNamespaceMap.containsKey(fqname.getNamespace())) {
          FullQualifiedName changedName = new FullQualifiedName(
              aliasNamespaceMap.get(fqname.getNamespace()), fqname.getName());
          entityType.setEdmBaseType(entityTypesMap.get(changedName));
        } else {// Adding dummy basetype which will fail during validation
          EdmEntityTypeImpl newBaseType = new EdmEntityTypeImpl();
          newBaseType.setName(fqname.getName());
          newBaseType.setNamespace(fqname.getNamespace());
          entityType.setEdmBaseType(newBaseType);
          break;
        }
      }
    }
    setBaseTypePropertiesForEntityType(entityBaseTypeMap);
  }

  private void setBaseTypePropertiesForEntityType(
      Map<FullQualifiedName, FullQualifiedName> baseTypeMap) throws EdmException{
    changeAliasNamespaces(baseTypeMap);
    while(!baseTypeMap.isEmpty()){
      Iterator<Entry<FullQualifiedName, FullQualifiedName>> iterator = baseTypeMap.entrySet().iterator();
      while (iterator.hasNext()) {
        Entry<FullQualifiedName, FullQualifiedName> baseType = iterator.next();
        if(baseTypeMap.get(baseType.getValue()) == null){
          EdmEntityType entityType = entityTypesMap.get(baseType.getKey());
          List<String> properties = entityType.getPropertyNames();
          if(entityTypesMap.get(baseType.getValue())!=null){ //NOSONAR
            properties.addAll(entityTypesMap.get(baseType.getValue()).getPropertyNames());
          }
          iterator.remove();
        }
      }
    }
  }
  private void setBaseTypePropertiesForComplexType(
      Map<FullQualifiedName, FullQualifiedName> baseTypeMap) throws EdmException{
    changeAliasNamespaces(baseTypeMap);
    while(!baseTypeMap.isEmpty()){
      Iterator<Entry<FullQualifiedName, FullQualifiedName>> iterator = baseTypeMap.entrySet().iterator();
      while (iterator.hasNext()) {
        Entry<FullQualifiedName, FullQualifiedName> baseType = iterator.next();
        if(baseTypeMap.get(baseType.getValue()) == null){
          EdmComplexType entityType = complexTypesMap.get(baseType.getKey());
          List<String> properties = entityType.getPropertyNames();
          if(complexTypesMap.get(baseType.getValue())!=null){ //NOSONAR
            properties.addAll(complexTypesMap.get(baseType.getValue()).getPropertyNames());
          }
          iterator.remove();
        }
      }
    }
  }
  private void changeAliasNamespaces(Map<FullQualifiedName, FullQualifiedName> baseTypeMap) {
    for(Entry<FullQualifiedName, FullQualifiedName> entry:baseTypeMap.entrySet()){
      if (aliasNamespaceMap.containsKey(entry.getKey().getNamespace())) {
        FullQualifiedName value = baseTypeMap.get(entry.getKey());
        FullQualifiedName name = new FullQualifiedName(aliasNamespaceMap.get(entry.getKey().getNamespace()), 
            entry.getKey().getName());
        baseTypeMap.put(name, value);
        baseTypeMap.remove(entry.getKey());
      }
      if (aliasNamespaceMap.containsKey(entry.getValue().getNamespace())) {
        FullQualifiedName value =  new FullQualifiedName(aliasNamespaceMap.get(entry.getValue().getNamespace()), 
            entry.getValue().getName());
        baseTypeMap.remove(entry.getKey());
        baseTypeMap.put(entry.getKey(), value);
      }
     
    }
    
  }

  private EdmUsingImpl readUsing(final XMLStreamReader reader, final String schemaNamespace)
      throws XMLStreamException, EntityProviderException {

    reader.require(XMLStreamConstants.START_ELEMENT, edmNamespace, XmlMetadataConstants.EDM_USING);

    EdmUsingImpl using = new EdmUsingImpl();
    using.setNamespace(reader.getAttributeValue(null, XmlMetadataConstants.EDM_SCHEMA_NAMESPACE));
    inscopeMap.get(schemaNamespace).add(using.getNamespace());
    using.setAlias(reader.getAttributeValue(null, XmlMetadataConstants.EDM_SCHEMA_ALIAS));
    using.setAnnotationAttributes(readAnnotationAttribute(reader));

    List<EdmAnnotationElement> annotationElements = new ArrayList<EdmAnnotationElement>();
    while (reader.hasNext() && !(reader.isEndElement() && edmNamespace.equals(reader.getNamespaceURI())
        && XmlMetadataConstants.EDM_USING.equals(reader.getLocalName()))) {

      reader.next();
      if (reader.isStartElement()) {
        extractNamespaces(reader);
        currentHandledStartTagName = reader.getLocalName();
        annotationElements.add(readAnnotationElement(reader));
      }
    }
    if (!annotationElements.isEmpty()) {
      using.setAnnotationElements(annotationElements);
    }

    if (using.getAlias() != null) {
      aliasNamespaceMap.put(using.getAlias(), using.getNamespace());
    }

    return using;
  }

  private EdmEntityContainer readEntityContainer(final XMLStreamReader reader, EdmImpl edm)//NOSONAR
      throws XMLStreamException, EntityProviderException, EdmException { 
    reader.require(XMLStreamConstants.START_ELEMENT, edmNamespace, XmlMetadataConstants.EDM_ENTITY_CONTAINER);
    EdmEntityContainerImpl container = new EdmEntityContainerImpl(edm);
    List<EdmAnnotationElement> annotationElements = new ArrayList<EdmAnnotationElement>();

    List<EdmEntitySet> edmEntitySets = new ArrayList<EdmEntitySet>();
    List<EdmFunctionImport> edmFunctionImports = new ArrayList<EdmFunctionImport>();
    List<EdmAssociationSet> edmAssociationSets = new ArrayList<EdmAssociationSet>();

    container.setName(reader.getAttributeValue(null, XmlMetadataConstants.EDM_NAME));
    if (reader.getAttributeValue(Edm.NAMESPACE_M_2007_08, XmlMetadataConstants.EDM_CONTAINER_IS_DEFAULT) != null) {
      container.setDefaultContainer(
          "true".equalsIgnoreCase(reader.getAttributeValue(Edm.NAMESPACE_M_2007_08,
              "IsDefaultEntityContainer")));
      defaultEdmEntityContainer = container;
    }
    container.setExtendz(reader.getAttributeValue(null, XmlMetadataConstants.EDM_CONTAINER_EXTENDZ));
    List<EdmAnnotationAttribute> annotationAttribute = readAnnotationAttribute(reader);
    EdmAnnotationsImpl annotations = new EdmAnnotationsImpl();
    annotations.setAnnotationAttributes(annotationAttribute);
    container.setAnnotations(annotations);

    while (reader.hasNext()
        && !(reader.isEndElement() && edmNamespace.equals(reader.getNamespaceURI())
            && XmlMetadataConstants.EDM_ENTITY_CONTAINER.equals(reader.getLocalName()))) {
      reader.next();
      if (reader.isStartElement()) {
        extractNamespaces(reader);
        currentHandledStartTagName = reader.getLocalName();
        if (XmlMetadataConstants.EDM_ENTITY_SET.equals(currentHandledStartTagName)) {
          EdmEntitySetImpl entity = readEntitySet(reader);
          entity.setEdmEntityContainer(container);
          edmEntitySets.add(entity);
        } else if (XmlMetadataConstants.EDM_ASSOCIATION_SET.equals(currentHandledStartTagName)) {
          EdmAssociationSet association = readAssociationSet(reader, edm);
          edmAssociationSets.add(association);
        } else if (XmlMetadataConstants.EDM_FUNCTION_IMPORT.equals(currentHandledStartTagName)) {
          edmFunctionImports.add(readFunctionImport(reader, edm));
        } else {
          annotationElements.add(readAnnotationElement(reader));
        }
      }
    }
    if (!annotationElements.isEmpty()) {
      annotations.setAnnotationElements(annotationElements);
      container.setAnnotations(annotations);
    }
    if (edmFunctionImports != null && !edmFunctionImports.isEmpty()) {
      setContainerInFunctionImport(edmFunctionImports, container);
    }
    if (edmAssociationSets != null && !edmAssociationSets.isEmpty()) {
      setConatinerInAssociationSet(edmAssociationSets, container);
    }
    container.setNamespace(currentNamespace);
    container.setEdmEntitySets(edmEntitySets).setEdmAssociationSets(edmAssociationSets).setEdmFunctionImports(
        edmFunctionImports);

    containerMap.put(new FullQualifiedName(currentNamespace, container.getName()),
        container);
    edmEntitySetList.addAll(edmEntitySets);
    edmFunctionImportList.addAll(edmFunctionImports);
    return container;
  }

  private void setConatinerInAssociationSet(List<EdmAssociationSet> edmAssociationSets,
      EdmEntityContainerImpl containerImpl) {
    for (EdmAssociationSet associationSet : edmAssociationSets) {
      EdmAssociationSetImpl assocationSetImpl = (EdmAssociationSetImpl) associationSet;
      assocationSetImpl.setEdmEntityContainer(containerImpl);
    }
    containerImpl.setEdmAssociationSetMap(associationSetMap);
  }

  /**
   * @param functionImports
   * @param containerImpl
   */
  private void setContainerInFunctionImport(List<EdmFunctionImport> functionImports,
      EdmEntityContainerImpl containerImpl) {
    for (EdmFunctionImport funcImport : functionImports) {
      EdmFunctionImportImpl functionImpl = (EdmFunctionImportImpl) funcImport;
      functionImpl.setEdmEntityContainer(containerImpl);
    }
  }

  private EdmFunctionImport readFunctionImport(final XMLStreamReader reader, EdmImpl edm)
      throws XMLStreamException, EntityProviderException, EdmException {
    reader.require(XMLStreamConstants.START_ELEMENT, edmNamespace, XmlMetadataConstants.EDM_FUNCTION_IMPORT);
    EdmFunctionImportImpl function = new EdmFunctionImportImpl();
    ArrayList<EdmFunctionImportParameter> functionImportParameters = new ArrayList<EdmFunctionImportParameter>();
    Map<String, ArrayList<EdmFunctionImportParameter>> functionParameters =
        new HashMap<String, ArrayList<EdmFunctionImportParameter>>();
    List<EdmAnnotationElement> annotationElements = new ArrayList<EdmAnnotationElement>();
    EdmAnnotationsImpl annotations = new EdmAnnotationsImpl();
    Map<String, EdmParameter> edmParamMap = new HashMap<String, EdmParameter>();
    List<String> parametersList =  new ArrayList<String>();
    FullQualifiedName fqName ;
    function.setName(reader.getAttributeValue(null, XmlMetadataConstants.EDM_NAME));
    function.setHttpMethod(reader.getAttributeValue(Edm.NAMESPACE_M_2007_08,
        XmlMetadataConstants.EDM_FUNCTION_IMPORT_HTTP_METHOD));
    String entitySet = reader.getAttributeValue(null, XmlMetadataConstants.EDM_ENTITY_SET);
    function.setEntitySet(entitySet);

    String returnTypeString = reader.getAttributeValue(null, XmlMetadataConstants.EDM_FUNCTION_IMPORT_RETURN);
    EdmTypedImpl returnType = new EdmTypedImpl();
    if (returnTypeString != null) {
      if (returnTypeString.startsWith("Collection") || returnTypeString.startsWith("collection")) {
        returnTypeString = returnTypeString.substring(returnTypeString.indexOf("(") + 1, returnTypeString.length() - 1);
        returnType.setMultiplicity(EdmMultiplicity.MANY);
      } else {
        returnType.setMultiplicity(EdmMultiplicity.ONE);
      }
      fqName = extractFQName(returnTypeString);
      returnType.setTypeName(fqName);
      ((EdmNamedImpl) returnType).setName(fqName.getName());
      ((EdmNamedImpl) returnType).setEdm(edm);
      function.setReturnType(returnType);
    }
    annotations.setAnnotationAttributes(readAnnotationAttribute(reader));
    while (reader.hasNext()
        && !(reader.isEndElement() && edmNamespace.equals(reader.getNamespaceURI())
            && XmlMetadataConstants.EDM_FUNCTION_IMPORT.equals(reader.getLocalName()))) {
      reader.next();
      if (reader.isStartElement()) {
        extractNamespaces(reader);
        currentHandledStartTagName = reader.getLocalName();
        if (XmlMetadataConstants.EDM_FUNCTION_PARAMETER.equals(currentHandledStartTagName)) {
          EdmFunctionImportParameter edmFunctionImportParameter = readFunctionImportParameter(reader);
          functionImportParameters.add(edmFunctionImportParameter);
          EdmParameterImpl edmParamImpl = new EdmParameterImpl();
          edmParamImpl.setEdm(edm);
          edmParamImpl.setName(edmFunctionImportParameter.getName());
          edmParamImpl.setFacets(edmFunctionImportParameter.getFacets());
          edmParamImpl.setMapping(edmFunctionImportParameter.getMapping());
          edmParamImpl.setMultiplicity(returnType.getMultiplicity());
          edmParamImpl.setParameter(edmFunctionImportParameter);
          edmParamImpl.setTypeName(edmFunctionImportParameter.getType().getFullQualifiedName());
          edmParamImpl.setAnnotations(edmFunctionImportParameter.getAnnotations());
          parametersList.add(edmParamImpl.getName());
          edmParamMap.put(edmFunctionImportParameter.getName(), edmParamImpl);
        } else {
          annotationElements.add(readAnnotationElement(reader));
        }
      }
    }
    if (!annotationElements.isEmpty()) {
      annotations.setAnnotationElements(annotationElements);
    }
    function.setAnnotations(annotations);
    functionParameters.put(function.getName(), functionImportParameters);
    function.setParameters(functionParameters);
    function.setEdmParameters(edmParamMap);
    function.setParametersList(parametersList);
    return function;
  }

  private EdmFunctionImportParameter readFunctionImportParameter(final XMLStreamReader reader)
      throws EntityProviderException, XMLStreamException, EdmException {
    reader.require(XMLStreamConstants.START_ELEMENT, edmNamespace, XmlMetadataConstants.EDM_FUNCTION_PARAMETER);
    EdmFunctionImportParameter functionParameter = new EdmFunctionImportParameter();
    EdmAnnotationsImpl annotations = new EdmAnnotationsImpl();
    List<EdmAnnotationElement> annotationElements = new ArrayList<EdmAnnotationElement>();

    functionParameter.setName(reader.getAttributeValue(null, XmlMetadataConstants.EDM_NAME));
    functionParameter.setMode(reader.getAttributeValue(null, XmlMetadataConstants.EDM_FUNCTION_PARAMETER_MODE));
    String type = reader.getAttributeValue(null, XmlMetadataConstants.EDM_TYPE);
    if (type == null) {
      throw new EntityProviderException(EntityProviderException.MISSING_ATTRIBUTE
          .addContent(XmlMetadataConstants.EDM_TYPE).addContent(XmlMetadataConstants.EDM_FUNCTION_PARAMETER));
    }
    functionParameter.setType(EdmSimpleTypeKind.valueOf(extractFQName(type).getName()));
    EdmFacets facets = readFacets(reader);
    functionParameter.setFacets(facets);
    annotations.setAnnotationAttributes(readAnnotationAttribute(reader));
    while (reader.hasNext()
        && !(reader.isEndElement() && edmNamespace.equals(reader.getNamespaceURI())
            && XmlMetadataConstants.EDM_FUNCTION_PARAMETER.equals(reader.getLocalName()))) {
      reader.next();
      if (reader.isStartElement()) {
        extractNamespaces(reader);
        annotationElements.add(readAnnotationElement(reader));
      }
    }
    if (!annotationElements.isEmpty()) {
      annotations.setAnnotationElements(annotationElements);
    }
    functionParameter.setAnnotations(annotations);
    return functionParameter;
  }

  private EdmAssociationSet readAssociationSet(final XMLStreamReader reader, EdmImpl edm)
      throws XMLStreamException, EntityProviderException, EdmException {
    reader.require(XMLStreamConstants.START_ELEMENT, edmNamespace, XmlMetadataConstants.EDM_ASSOCIATION_SET);
    EdmAssociationSetImpl associationSet = new EdmAssociationSetImpl();
    List<EdmAssociationSetEnd> ends = new ArrayList<EdmAssociationSetEnd>();
    EdmAnnotationsImpl annotation = new EdmAnnotationsImpl();
    String entitySetName = null;
    List<EdmAnnotationElement> annotationElements = new ArrayList<EdmAnnotationElement>();
    associationSet.setName(reader.getAttributeValue(null, XmlMetadataConstants.EDM_NAME));
    String association = reader.getAttributeValue(null, XmlMetadataConstants.EDM_ASSOCIATION);
    if (association != null) {
      associationSet.setAssociation(extractFQName(association));
    } else {
      throw new EntityProviderException(EntityProviderException.MISSING_ATTRIBUTE
          .addContent(XmlMetadataConstants.EDM_ASSOCIATION).addContent(XmlMetadataConstants.EDM_ASSOCIATION_SET));
    }
    annotation.setAnnotationAttributes(readAnnotationAttribute(reader));
    while (reader.hasNext()
        && !(reader.isEndElement() && edmNamespace.equals(reader.getNamespaceURI())
            && XmlMetadataConstants.EDM_ASSOCIATION_SET.equals(reader.getLocalName()))) {
      reader.next();
      if (reader.isStartElement()) {
        extractNamespaces(reader);
        currentHandledStartTagName = reader.getLocalName();
        if (XmlMetadataConstants.EDM_ASSOCIATION_END.equals(currentHandledStartTagName)) {
          EdmAssociationSetEndImpl associationSetEnd = new EdmAssociationSetEndImpl();
          entitySetName = reader.getAttributeValue(null, XmlMetadataConstants.EDM_ENTITY_SET);
          associationSetEnd.setEntitySetName(entitySetName);
          associationSetEnd.setRole(reader.getAttributeValue(null, XmlMetadataConstants.EDM_ROLE));
          ends.add(associationSetEnd);
          List<EdmAssociationSetEndImpl> associationSetEndList;
          if(associationSetEndMap.get(entitySetName) == null){
            associationSetEndList = new ArrayList<EdmAssociationSetEndImpl>();
          }else{
            associationSetEndList= associationSetEndMap.get(entitySetName);
          }
          associationSetEndList.add(associationSetEnd);
          associationSetEndMap.put(entitySetName, associationSetEndList);
        } else {
          annotationElements.add(readAnnotationElement(reader));
        }
      }
    }
    if (ends.size() != 2) {
      throw new EntityProviderException(EntityProviderException.ILLEGAL_ARGUMENT
          .addContent("Count of AssociationSet ends should be 2"));
    } else {
      associationSet.setEnd1(ends.get(0));
      associationSet.setEnd2(ends.get(1));
    }
    if (!annotationElements.isEmpty()) {
      annotation.setAnnotationElements(annotationElements);
    }
    //Updating temporary map for association post processing
    tempAssociationSetMap.put(associationSet.getAssociationSetFQName().toString(), associationSet);
    associationSet.setAnnotations(annotation);
    associationSet.setEdm(edm);
    return associationSet;
  }

  private EdmEntitySetImpl readEntitySet(final XMLStreamReader reader) throws XMLStreamException,
  EntityProviderException, EdmException {
    reader.require(XMLStreamConstants.START_ELEMENT, edmNamespace, XmlMetadataConstants.EDM_ENTITY_SET);
    EdmEntitySetImpl entitySet = new EdmEntitySetImpl();
    EdmAnnotationsImpl annotations = new EdmAnnotationsImpl();
    List<EdmAnnotationElement> annotationElements = new ArrayList<EdmAnnotationElement>();
    entitySet.setName(reader.getAttributeValue(null, XmlMetadataConstants.EDM_NAME));
    String entityType = reader.getAttributeValue(null, XmlMetadataConstants.EDM_ENTITY_TYPE);
    if (entityType != null) {
      FullQualifiedName fqName = extractFQName(entityType);
      entitySet.setEdmEntityTypeName(fqName);
    } else {
      throw new EntityProviderException(EntityProviderException.MISSING_ATTRIBUTE
          .addContent(XmlMetadataConstants.EDM_ENTITY_TYPE).addContent(XmlMetadataConstants.EDM_ENTITY_SET));
    }
    annotations.setAnnotationAttributes(readAnnotationAttribute(reader));
    while (reader.hasNext()
        && !(reader.isEndElement() && edmNamespace.equals(reader.getNamespaceURI())
            && XmlMetadataConstants.EDM_ENTITY_SET.equals(reader.getLocalName()))) {
      reader.next();
      if (reader.isStartElement()) {
        extractNamespaces(reader);
        annotationElements.add(readAnnotationElement(reader));
      }
    }
    if (!annotationElements.isEmpty()) {
      annotations.setAnnotationElements(annotationElements);
    }
    entitySet.setAnnotations(annotations);
    return entitySet;
  }

  private EdmAssociation readAssociation(final XMLStreamReader reader, EdmImpl edm)
      throws XMLStreamException, EntityProviderException, EdmException {
    reader.require(XMLStreamConstants.START_ELEMENT, edmNamespace, XmlMetadataConstants.EDM_ASSOCIATION);

    EdmAssociationImpl association = new EdmAssociationImpl();
    association.setName(reader.getAttributeValue(null, XmlMetadataConstants.EDM_NAME));
    association.setNamespace(currentNamespace);
    List<EdmMetadataAssociationEnd> associationEnds = new ArrayList<EdmMetadataAssociationEnd>();
    List<EdmAnnotationElement> annotationElements = new ArrayList<EdmAnnotationElement>();
    EdmAnnotationsImpl annotations = new EdmAnnotationsImpl();
    annotations.setAnnotationAttributes(readAnnotationAttribute(reader));
    while (reader.hasNext()
        && !(reader.isEndElement() && edmNamespace.equals(reader.getNamespaceURI())
            && XmlMetadataConstants.EDM_ASSOCIATION.equals(reader.getLocalName()))) {
      reader.next();
      if (reader.isStartElement()) {
        extractNamespaces(reader);
        currentHandledStartTagName = reader.getLocalName();
        if (XmlMetadataConstants.EDM_ASSOCIATION_END.equals(currentHandledStartTagName)) {
          associationEnds.add(readAssociationEnd(reader, edm));
        } else if (XmlMetadataConstants.EDM_ASSOCIATION_CONSTRAINT.equals(currentHandledStartTagName)) {
          association.setReferentialConstraint(readReferentialConstraint(reader));
        } else {
          annotationElements.add(readAnnotationElement(reader));
        }
      }
    }
    if (associationEnds.size() < 2 && associationEnds.size() > 2) {
      throw new EntityProviderException(EntityProviderException.ILLEGAL_ARGUMENT
          .addContent("Count of association ends should be 2"));
    }
    if (!annotationElements.isEmpty()) {
      annotations.setAnnotationElements(annotationElements);
    }
    association.setAnnotations(annotations);
    association.setEdm(edm);
    association.setAssociationEnds(associationEnds);
    associationsMap.put(new FullQualifiedName(currentNamespace, association.getName()), association);
    return association;
  }

  private EdmReferentialConstraint readReferentialConstraint(final XMLStreamReader reader) throws XMLStreamException,
      EntityProviderException, EdmException {
    reader.require(XMLStreamConstants.START_ELEMENT, edmNamespace, XmlMetadataConstants.EDM_ASSOCIATION_CONSTRAINT);
    EdmReferentialConstraintImpl refConstraint = new EdmReferentialConstraintImpl();
    List<EdmAnnotationElement> annotationElements = new ArrayList<EdmAnnotationElement>();
    EdmAnnotationsImpl annotations = new EdmAnnotationsImpl();
    annotations.setAnnotationAttributes(readAnnotationAttribute(reader));
    while (reader.hasNext()
        && !(reader.isEndElement() && edmNamespace.equals(reader.getNamespaceURI())
            && XmlMetadataConstants.EDM_ASSOCIATION_CONSTRAINT.equals(reader.getLocalName()))) {
      reader.next();
      if (reader.isStartElement()) {
        extractNamespaces(reader);
        currentHandledStartTagName = reader.getLocalName();
        if (XmlMetadataConstants.EDM_ASSOCIATION_PRINCIPAL.equals(currentHandledStartTagName)) {
          reader
              .require(XMLStreamConstants.START_ELEMENT, edmNamespace, XmlMetadataConstants.EDM_ASSOCIATION_PRINCIPAL);
          refConstraint.setPrincipal(readReferentialConstraintRole(reader));
        } else if (XmlMetadataConstants.EDM_ASSOCIATION_DEPENDENT.equals(currentHandledStartTagName)) {
          reader
              .require(XMLStreamConstants.START_ELEMENT, edmNamespace, XmlMetadataConstants.EDM_ASSOCIATION_DEPENDENT);
          refConstraint.setDependent(readReferentialConstraintRole(reader));
        } else {
          annotationElements.add(readAnnotationElement(reader));
        }
      }
    }
    if (!annotationElements.isEmpty()) {
      annotations.setAnnotationElements(annotationElements);
    }
    refConstraint.setAnnotations(annotations);
    return refConstraint;
  }

  private EdmReferentialConstraintRole readReferentialConstraintRole(final XMLStreamReader reader)
      throws EntityProviderException, XMLStreamException, EdmException {
    EdmReferentialConstraintRoleImpl rcRole = new EdmReferentialConstraintRoleImpl();
    rcRole.setRoleName(reader.getAttributeValue(null, XmlMetadataConstants.EDM_ROLE));
    List<EdmPropertyImpl> properties = new ArrayList<EdmPropertyImpl>();
    List<EdmAnnotationElement> annotationElements = new ArrayList<EdmAnnotationElement>();
    List<String> refNames = new ArrayList<String>();
    EdmAnnotationsImpl annotations = new EdmAnnotationsImpl();
    annotations.setAnnotationAttributes(readAnnotationAttribute(reader));
    while (reader.hasNext() && !(reader.isEndElement() && edmNamespace.equals(reader.getNamespaceURI())
        && (XmlMetadataConstants.EDM_ASSOCIATION_PRINCIPAL.equals(reader.getLocalName())
            || XmlMetadataConstants.EDM_ASSOCIATION_DEPENDENT.equals(reader.getLocalName())))) {
      reader.next();
      if (reader.isStartElement()) {
        extractNamespaces(reader);
        currentHandledStartTagName = reader.getLocalName();
        if (XmlMetadataConstants.EDM_PROPERTY_REF.equals(currentHandledStartTagName)) {
          EdmPropertyImpl property = (EdmPropertyImpl) readPropertyRef(reader);
          properties.add(property );
          refNames.add(property.getName());
        } else {
          annotationElements.add(readAnnotationElement(reader));
        }
      }
    }
    if (!annotationElements.isEmpty()) {
      annotations.setAnnotationElements(annotationElements);
    }
    rcRole.setProperty(properties);
    rcRole.setRefNames(refNames);
    rcRole.setAnnotations(annotations);
    return rcRole;
  }

  private EdmComplexType readComplexType(final XMLStreamReader reader, Edm edm)
      throws XMLStreamException, EntityProviderException, EdmException {
    reader.require(XMLStreamConstants.START_ELEMENT, edmNamespace, XmlMetadataConstants.EDM_COMPLEX_TYPE);

    EdmComplexTypeImpl complexType = new EdmComplexTypeImpl();
    complexType.setEdmTypeKind(EdmTypeKind.COMPLEX);
    List<EdmProperty> properties = new ArrayList<EdmProperty>();
    List<EdmAnnotationElement> annotationElements = new ArrayList<EdmAnnotationElement>();
    complexType.setName(reader.getAttributeValue(null, XmlMetadataConstants.EDM_NAME));
    complexType.setNamespace(currentNamespace);
    String baseType = reader.getAttributeValue(null, XmlMetadataConstants.EDM_BASE_TYPE);
    if (baseType != null) {
      FullQualifiedName fqname = extractFQName(baseType);
      complexType.setBaseTypeName(fqname);
      complexBaseTypeMap.put(new FullQualifiedName(complexType.getNamespace(), complexType.getName()), fqname);
    }
    if (reader.getAttributeValue(null, XmlMetadataConstants.EDM_TYPE_ABSTRACT) != null) {
      complexType.setAbstract("true".equalsIgnoreCase(reader.getAttributeValue(null,
          XmlMetadataConstants.EDM_TYPE_ABSTRACT)));
    }
    EdmAnnotationsImpl annotations = new EdmAnnotationsImpl();
    annotations.setAnnotationAttributes(readAnnotationAttribute(reader));
    while (reader.hasNext()
        && !(reader.isEndElement() && edmNamespace.equals(reader.getNamespaceURI())
            && XmlMetadataConstants.EDM_COMPLEX_TYPE.equals(reader.getLocalName()))) {
      reader.next();
      if (reader.isStartElement()) {
        extractNamespaces(reader);
        currentHandledStartTagName = reader.getLocalName();
        if (XmlMetadataConstants.EDM_PROPERTY.equals(currentHandledStartTagName)) {
          properties.add(readProperty(reader, edm));
        } else {
          annotationElements.add(readAnnotationElement(reader));
        }
      }
    }
    if (!annotationElements.isEmpty()) {
      annotations.setAnnotationElements(annotationElements);
    }
    complexType.setProperties(properties);
    List<String> edmPropertyNames = new ArrayList<String>();
    for (EdmProperty name : properties) {
      edmPropertyNames.add(name.getName());
    }
    complexType.setEdmPropertyNames(edmPropertyNames);
    if (complexType.getName() != null) {
      FullQualifiedName fqName = new FullQualifiedName(currentNamespace, complexType.getName());
      complexTypesMap.put(fqName, complexType);
      if (complexPropertyMap.get(fqName) != null) {// POST processing to set the edm types for complex types
        ((EdmComplexPropertyImpl) complexPropertyMap.get(fqName)).setEdmType(complexType);
      }
    } else {
      throw new EntityProviderException(EntityProviderException.MISSING_ATTRIBUTE.addContent("Name"));
    }
    return complexType;

  }

  private EdmEntityType readEntityType(final XMLStreamReader reader,
      EdmImpl edm) throws XMLStreamException, EntityProviderException, EdmException {
    reader.require(XMLStreamConstants.START_ELEMENT, edmNamespace, XmlMetadataConstants.EDM_ENTITY_TYPE);
    EdmEntityTypeImpl entityType = new EdmEntityTypeImpl();
    List<EdmProperty> properties = new ArrayList<EdmProperty>();
    List<String> edmPropertyNames = new ArrayList<String>();
    List<EdmNavigationProperty> edmNavProperties = new ArrayList<EdmNavigationProperty>();
    List<EdmAnnotationElement> annotationElements = new ArrayList<EdmAnnotationElement>();
    List<String> edmNavigationPropertyNames = new ArrayList<String>();
    List<String> edmKeyPropertyNames = new ArrayList<String>();
    List<EdmProperty> edmKeyProperties = new ArrayList<EdmProperty>();
    EdmKeyImpl key = null;
    ((EdmNamedImpl) entityType).setName(reader.getAttributeValue(null, XmlMetadataConstants.EDM_NAME));
    entityType.setNamespace(currentNamespace);
    entityType.setEdm((EdmImpl) edm);
    String hasStream = reader.getAttributeValue(Edm.NAMESPACE_M_2007_08, XmlMetadataConstants.M_ENTITY_TYPE_HAS_STREAM);
    if (hasStream != null) {
      entityType.setHasStream("true".equalsIgnoreCase(hasStream));
    }

    if (reader.getAttributeValue(null, XmlMetadataConstants.EDM_TYPE_ABSTRACT) != null) {
      entityType.setAbstract("true".equalsIgnoreCase(reader.getAttributeValue(null,
          XmlMetadataConstants.EDM_TYPE_ABSTRACT)));
    }
    String baseType = reader.getAttributeValue(null, XmlMetadataConstants.EDM_BASE_TYPE);
    if (baseType != null) {
      FullQualifiedName fqName = extractFQName(baseType);
      entityType.setBaseType(fqName);
      //Populating base type map with child and parent for multiple inheritance
      entityBaseTypeMap.put(extractFQNameFromEntityType(entityType), fqName);
    }
    entityType.setCustomizableFeedMappings(readCustomizableFeedMappings(reader));
    EdmAnnotationsImpl annotations = new EdmAnnotationsImpl();
    annotations.setAnnotationAttributes(readAnnotationAttribute(reader));
    while (reader.hasNext()
        && !(reader.isEndElement() && edmNamespace.equals(reader.getNamespaceURI())
            && XmlMetadataConstants.EDM_ENTITY_TYPE.equals(reader.getLocalName()))) {
      reader.next();
      if (reader.isStartElement()) {
        currentHandledStartTagName = reader.getLocalName();
        if (XmlMetadataConstants.EDM_ENTITY_TYPE_KEY.equals(currentHandledStartTagName)) {
          key = readEntityTypeKey(reader);
        } else if (XmlMetadataConstants.EDM_PROPERTY.equals(currentHandledStartTagName)) {
          properties.add(readProperty(reader, edm));
        } else if (XmlMetadataConstants.EDM_NAVIGATION_PROPERTY.equals(currentHandledStartTagName)) {
          edmNavProperties.add(readNavigationProperty(reader, edm));
        } else {
          annotationElements.add(readAnnotationElement(reader));
        }
        extractNamespaces(reader);
      }
    }
    if (!annotationElements.isEmpty()) {
      annotations.setAnnotationElements(annotationElements);
    }
    
    for (EdmProperty property : properties) {
      edmPropertyNames.add(property.getName());
      if (key != null) {        
        for(EdmProperty keyProperty: key.getKeys()){
          if(property.getName().equals(keyProperty.getName())){
            edmKeyPropertyNames.add(keyProperty.getName());
            edmKeyProperties.add(property);
          }
        }
      }
    }
    for ( EdmNavigationProperty navigations : edmNavProperties){
      edmNavigationPropertyNames.add(navigations.getName());
    }

    entityType.setAnnotations(annotations);
    entityType.setEdmKeyProperties(edmKeyProperties);
    entityType.setNavigationProperties(edmNavProperties);
    entityType.setEdmNavigationPropertyNames(edmNavigationPropertyNames);    
    entityType.setEdmKeyPropertyNames(edmKeyPropertyNames);
    entityType.setProperties(properties);
    entityType.setEdmPropertyNames(edmPropertyNames);
    if (entityType.getName() != null) {
      FullQualifiedName fqName = new FullQualifiedName(currentNamespace, entityType.getName());
      entityTypesMap.put(fqName, entityType);
    } else {
      throw new EntityProviderException(EntityProviderException.MISSING_ATTRIBUTE.addContent("Name"));
    }

    return entityType;
  }

  private EdmKeyImpl readEntityTypeKey(final XMLStreamReader reader) throws XMLStreamException,
  EntityProviderException, EdmException {
    reader.require(XMLStreamConstants.START_ELEMENT, edmNamespace, XmlMetadataConstants.EDM_ENTITY_TYPE_KEY);
    List<EdmProperty> keys = new ArrayList<EdmProperty>();
    List<EdmAnnotationElement> annotationElements = new ArrayList<EdmAnnotationElement>();
    List<EdmAnnotationAttribute> annotationAttributes = readAnnotationAttribute(reader);
    while (reader.hasNext()
        && !(reader.isEndElement() && edmNamespace.equals(reader.getNamespaceURI())
            && XmlMetadataConstants.EDM_ENTITY_TYPE_KEY.equals(reader.getLocalName()))) {
      reader.next();
      if (reader.isStartElement()) {
        extractNamespaces(reader);
        currentHandledStartTagName = reader.getLocalName();
        if (XmlMetadataConstants.EDM_PROPERTY_REF.equals(currentHandledStartTagName)) {
          reader.require(XMLStreamConstants.START_ELEMENT, edmNamespace, XmlMetadataConstants.EDM_PROPERTY_REF);
          keys.add(readPropertyRef(reader));
        } else {
          annotationElements.add(readAnnotationElement(reader));
        }
      }
    }
    EdmKeyImpl key = new EdmKeyImpl().setKeys(keys).setAnnotationAttributes(annotationAttributes);
    if (!annotationElements.isEmpty()) {
      key.setAnnotationElements(annotationElements);
    }
    return key;
  }

  private EdmProperty readPropertyRef(final XMLStreamReader reader) throws XMLStreamException, 
  EntityProviderException, EdmException {
    reader.require(XMLStreamConstants.START_ELEMENT, edmNamespace, XmlMetadataConstants.EDM_PROPERTY_REF);
    EdmPropertyImpl propertyRef = new EdmPropertyRefImpl();
    propertyRef.setName(reader.getAttributeValue(null, XmlMetadataConstants.EDM_NAME));
    EdmAnnotations annotations = new EdmAnnotationsImpl();
    List<EdmAnnotationElement> annotationElements = new ArrayList<EdmAnnotationElement>();
    ((EdmAnnotationsImpl) annotations).setAnnotationAttributes(readAnnotationAttribute(reader));
    while (reader.hasNext() && !(reader.isEndElement() && edmNamespace.equals(reader.getNamespaceURI())
        && XmlMetadataConstants.EDM_PROPERTY_REF.equals(reader.getLocalName()))) {
      reader.next();
      if (reader.isStartElement()) {
        extractNamespaces(reader);
        annotationElements.add(readAnnotationElement(reader));
      }
    }
    if (!annotationElements.isEmpty()) {
      ((EdmAnnotationsImpl) annotations).setAnnotationElements(annotationElements);
    }
    propertyRef.setAnnotations(annotations);
    return propertyRef;
  }

  private EdmNavigationProperty readNavigationProperty(final XMLStreamReader reader, EdmImpl edm) 
      throws XMLStreamException, EntityProviderException, EdmException {
    reader.require(XMLStreamConstants.START_ELEMENT, edmNamespace, XmlMetadataConstants.EDM_NAVIGATION_PROPERTY);

    EdmNavigationPropertyImpl navProperty = new EdmNavigationPropertyImpl();
    List<EdmAnnotationElement> annotationElements = new ArrayList<EdmAnnotationElement>();
    navProperty.setName(reader.getAttributeValue(null, XmlMetadataConstants.EDM_NAME));
    String relationship = reader.getAttributeValue(null, XmlMetadataConstants.EDM_NAVIGATION_RELATIONSHIP);
    if (relationship != null) {
      FullQualifiedName fqName = extractFQName(relationship);
      navProperty.setRelationshipName(fqName);

    } else {
      throw new EntityProviderException(EntityProviderException.MISSING_ATTRIBUTE
          .addContent(XmlMetadataConstants.EDM_NAVIGATION_RELATIONSHIP).addContent(
              XmlMetadataConstants.EDM_NAVIGATION_PROPERTY));
    }

    navProperty.setFromRole(reader.getAttributeValue(null, XmlMetadataConstants.EDM_NAVIGATION_FROM_ROLE));
    navProperty.setToRole(reader.getAttributeValue(null, XmlMetadataConstants.EDM_NAVIGATION_TO_ROLE));
    EdmAnnotations annotations = new EdmAnnotationsImpl();
    ((EdmAnnotationsImpl) annotations).setAnnotationAttributes(readAnnotationAttribute(reader));
    while (reader.hasNext() && !(reader.isEndElement() && edmNamespace.equals(reader.getNamespaceURI())
        && XmlMetadataConstants.EDM_NAVIGATION_PROPERTY.equals(reader.getLocalName()))) {
      reader.next();
      if (reader.isStartElement()) {
        extractNamespaces(reader);
        annotationElements.add(readAnnotationElement(reader));
      }
    }
    if (!annotationElements.isEmpty()) {
      ((EdmAnnotationsImpl) annotations).setAnnotationElements(annotationElements);
    }
    navProperty.setAnnotations(annotations);
    navProperty.setEdm(edm);        
    navProperties.add(navProperty);
    return navProperty;
  }

  private EdmProperty readProperty(final XMLStreamReader reader, Edm edm)
      throws XMLStreamException, EntityProviderException, EdmException {
    reader.require(XMLStreamConstants.START_ELEMENT, edmNamespace, XmlMetadataConstants.EDM_PROPERTY);
    EdmPropertyImpl property;
    List<EdmAnnotationElement> annotationElements = new ArrayList<EdmAnnotationElement>();
    String type = reader.getAttributeValue(null, XmlMetadataConstants.EDM_TYPE);
    if (type == null) {
      throw new EntityProviderException(EntityProviderException.MISSING_ATTRIBUTE
          .addContent(XmlMetadataConstants.EDM_TYPE).addContent(XmlMetadataConstants.EDM_PROPERTY));
    }
    FullQualifiedName fqName = extractFQName(type);

    if (EdmSimpleType.EDM_NAMESPACE.equals(fqName.getNamespace())) {
      property = readSimpleProperty(reader, fqName);
    } else {
      property = readComplexProperty(reader, fqName, edm);
    }
    property.setFacets(readFacets(reader));
    property.setCustomizableFeedMappings(readCustomizableFeedMappings(reader));
    property.setMimeType(reader.getAttributeValue(Edm.NAMESPACE_M_2007_08, XmlMetadataConstants.M_MIMETYPE));
    EdmAnnotations annotations = new EdmAnnotationsImpl();
    ((EdmAnnotationsImpl) annotations).setAnnotationAttributes(readAnnotationAttribute(reader));
    while (reader.hasNext() && !(reader.isEndElement() && edmNamespace.equals(reader.getNamespaceURI())
        && XmlMetadataConstants.EDM_PROPERTY.equals(reader.getLocalName()))) {
      reader.next();
      if (reader.isStartElement()) {
        extractNamespaces(reader);
        annotationElements.add(readAnnotationElement(reader));
      }
    }
    if (!annotationElements.isEmpty()) {
      ((EdmAnnotationsImpl) annotations).setAnnotationElements(annotationElements);
    }
    property.setAnnotations(annotations);
    return property;
  }

  private EdmPropertyImpl readComplexProperty(final XMLStreamReader reader, final FullQualifiedName fqName, Edm edm)
      throws XMLStreamException, EdmException {
    EdmComplexPropertyImpl property = new EdmComplexPropertyImpl();
    property.setName(reader.getAttributeValue(null, XmlMetadataConstants.EDM_NAME));
    property.setTypeName(fqName);
    property.setEdm((EdmImpl) edm);
    complexPropertyMap.put(fqName, property);
    return property;
  }

  private EdmPropertyImpl readSimpleProperty(final XMLStreamReader reader, final FullQualifiedName fqName)
      throws XMLStreamException, EdmException {
    EdmSimplePropertyImpl property = new EdmSimplePropertyImpl();
    property.setName(reader.getAttributeValue(null, XmlMetadataConstants.EDM_NAME));
    property.setTypeName(fqName);
    property.setSimpleType(EdmSimpleTypeKind.valueOf(fqName.getName()));
    property.setEdmType(EdmSimpleTypeFacadeImpl.getEdmSimpleType(property.getSimpleType()));
    return property;
  }

  private EdmFacets readFacets(final XMLStreamReader reader) throws XMLStreamException {
    String isNullable = reader.getAttributeValue(null, XmlMetadataConstants.EDM_PROPERTY_NULLABLE);
    String maxLength = reader.getAttributeValue(null, XmlMetadataConstants.EDM_PROPERTY_MAX_LENGTH);
    String precision = reader.getAttributeValue(null, XmlMetadataConstants.EDM_PROPERTY_PRECISION);
    String scale = reader.getAttributeValue(null, XmlMetadataConstants.EDM_PROPERTY_SCALE);
    String isFixedLength = reader.getAttributeValue(null, XmlMetadataConstants.EDM_PROPERTY_FIXED_LENGTH);
    String isUnicode = reader.getAttributeValue(null, XmlMetadataConstants.EDM_PROPERTY_UNICODE);
    String concurrencyMode = reader.getAttributeValue(null, XmlMetadataConstants.EDM_PROPERTY_CONCURRENCY_MODE);
    String defaultValue = reader.getAttributeValue(null, XmlMetadataConstants.EDM_PROPERTY_DEFAULT_VALUE);
    String collation = reader.getAttributeValue(null, XmlMetadataConstants.EDM_PROPERTY_COLLATION);
    if (isNullable != null || maxLength != null || precision != null || scale != null || isFixedLength != null
        || isUnicode != null || concurrencyMode != null || defaultValue != null || collation != null) {
      EdmFacets facets = new Facets();
      if (isNullable != null) {
        ((Facets) facets).setNullable("true".equalsIgnoreCase(isNullable));
      }
      if (maxLength != null) {
        if (XmlMetadataConstants.EDM_PROPERTY_MAX_LENGTH_MAX_VALUE_FIRST_UPPERCASE.equals(maxLength)
            || XmlMetadataConstants.EDM_PROPERTY_MAX_LENGTH_MAX_VALUE_LOWERCASE.equals(maxLength)) {
          ((Facets) facets).setMaxLength(Integer.MAX_VALUE);
        } else {
          ((Facets) facets).setMaxLength(Integer.parseInt(maxLength));
        }
      }
      if (precision != null) {
        ((Facets) facets).setPrecision(Integer.parseInt(precision));
      }
      if (scale != null) {
        ((Facets) facets).setScale(Integer.parseInt(scale));
      }
      if (isFixedLength != null) {
        ((Facets) facets).setFixedLength("true".equalsIgnoreCase(isFixedLength));
      }
      if (isUnicode != null) {
        ((Facets) facets).setUnicode("true".equalsIgnoreCase(isUnicode));
      }
      for (int i = 0; i < EdmConcurrencyMode.values().length; i++) {
        if (EdmConcurrencyMode.values()[i].name().equalsIgnoreCase(concurrencyMode)) {
          ((Facets) facets).setConcurrencyMode(EdmConcurrencyMode.values()[i]);
        }
      }
      ((Facets) facets).setDefaultValue(defaultValue);
      ((Facets) facets).setCollation(collation);
      return facets;
    } else {
      return null;
    }
  }

  private EdmCustomizableFeedMappings readCustomizableFeedMappings(final XMLStreamReader reader) {
    String targetPath = reader.getAttributeValue(Edm.NAMESPACE_M_2007_08, XmlMetadataConstants.M_FC_TARGET_PATH);
    String sourcePath = reader.getAttributeValue(Edm.NAMESPACE_M_2007_08, XmlMetadataConstants.M_FC_SOURCE_PATH);
    String nsUri = reader.getAttributeValue(Edm.NAMESPACE_M_2007_08, XmlMetadataConstants.M_FC_NS_URI);
    String nsPrefix = reader.getAttributeValue(Edm.NAMESPACE_M_2007_08, XmlMetadataConstants.M_FC_PREFIX);
    String keepInContent = reader.getAttributeValue(Edm.NAMESPACE_M_2007_08, XmlMetadataConstants.M_FC_KEEP_IN_CONTENT);
    String contentKind = reader.getAttributeValue(Edm.NAMESPACE_M_2007_08, XmlMetadataConstants.M_FC_CONTENT_KIND);

    if (targetPath != null || sourcePath != null || nsUri != null || nsPrefix != null || keepInContent != null
        || contentKind != null) {
      EdmCustomizableFeedMappings feedMapping = new EdmCustomizableFeedMappingsImpl();
      EdmCustomizableFeedMappingsImpl feedMappingImpl = (EdmCustomizableFeedMappingsImpl) feedMapping;
      if (keepInContent != null) {
        feedMappingImpl.setFcKeepInContent("true".equals(keepInContent));
      }
      for (int i = 0; i < EdmContentKind.values().length; i++) {
        if (EdmContentKind.values()[i].name().equalsIgnoreCase(contentKind)) {
          feedMappingImpl.setFcContentKind(EdmContentKind.values()[i]);
        }
      }
      feedMappingImpl.setFcTargetPath(targetPath).setFcSourcePath(sourcePath).setFcNsUri(nsUri).setFcNsPrefix(nsPrefix);
      return feedMapping;
    } else {
      return null;
    }

  }

  private EdmMetadataAssociationEnd readAssociationEnd(final XMLStreamReader reader, 
      EdmImpl edm) throws EntityProviderException, XMLStreamException {
    reader.require(XMLStreamConstants.START_ELEMENT, edmNamespace, XmlMetadataConstants.EDM_ASSOCIATION_END);

    EdmAssociationEndImpl associationEnd = new EdmAssociationEndImpl();
    EdmAnnotationsImpl annotations = new EdmAnnotationsImpl();
    List<EdmAnnotationElement> annotationElements = new ArrayList<EdmAnnotationElement>();
    associationEnd.setRole(reader.getAttributeValue(null, XmlMetadataConstants.EDM_ROLE));
    associationEnd.setMultiplicity(EdmMultiplicity.fromLiteral(reader.getAttributeValue(null,
        XmlMetadataConstants.EDM_ASSOCIATION_MULTIPLICITY)));
    String type = reader.getAttributeValue(null, XmlMetadataConstants.EDM_TYPE);
    if (type == null) {
      throw new EntityProviderException(EntityProviderException.MISSING_ATTRIBUTE
          .addContent(XmlMetadataConstants.EDM_TYPE).addContent(XmlMetadataConstants.EDM_ASSOCIATION_END));
    }
    associationEnd.setEdm(edm);
    associationEnd.setType(extractFQName(type));
    annotations.setAnnotationAttributes(readAnnotationAttribute(reader));
    while (reader.hasNext() && !(reader.isEndElement() && edmNamespace.equals(reader.getNamespaceURI())
        && XmlMetadataConstants.EDM_ASSOCIATION_END.equals(reader.getLocalName()))) {
      reader.next();
      if (reader.isStartElement()) {
        extractNamespaces(reader);
        currentHandledStartTagName = reader.getLocalName();
        if (XmlMetadataConstants.EDM_ASSOCIATION_ONDELETE.equals(currentHandledStartTagName)) {
          EdmOnDeleteImpl onDelete = new EdmOnDeleteImpl();
          for (int i = 0; i < EdmAction.values().length; i++) {
            if (EdmAction.values()[i].name().equalsIgnoreCase(
                reader.getAttributeValue(null, XmlMetadataConstants.EDM_ONDELETE_ACTION))) {
              onDelete.setAction(EdmAction.values()[i]);
            }
          }
          associationEnd.setOnDelete(onDelete);
        } else {
          annotationElements.add(readAnnotationElement(reader));
        }
      }
    }
    if (!annotationElements.isEmpty()) {
      annotations.setAnnotationElements(annotationElements);
    }
    associationEnd.setAnnotations(annotations);
    return associationEnd;
  }

  private EdmAnnotationElement readAnnotationElement(final XMLStreamReader reader) throws XMLStreamException {
    EdmAnnotationElementImpl elementImpl = new EdmAnnotationElementImpl();
    List<EdmAnnotationElement> annotationElements = new ArrayList<EdmAnnotationElement>();
    List<EdmAnnotationAttribute> annotationAttributes = new ArrayList<EdmAnnotationAttribute>();
    elementImpl.setName(reader.getLocalName());
    String elementNamespace = reader.getNamespaceURI();
    if (!edmNamespaces.contains(elementNamespace)) {
      elementImpl.setPrefix(reader.getPrefix());
      elementImpl.setNamespace(elementNamespace);
    }
    for (int i = 0; i < reader.getAttributeCount(); i++) {
      EdmAnnotationAttributeImpl annotationAttribute = new EdmAnnotationAttributeImpl();
      annotationAttribute.setText(reader.getAttributeValue(i));
      annotationAttribute.setName(reader.getAttributeLocalName(i));
      annotationAttribute.setPrefix(reader.getAttributePrefix(i));
      String namespace = reader.getAttributeNamespace(i);
      if (namespace != null && !isDefaultNamespace(namespace)) {
        annotationAttribute.setNamespace(namespace);
      }
      annotationAttributes.add(annotationAttribute);
    }
    if (!annotationAttributes.isEmpty()) {
      elementImpl.setAttributes(annotationAttributes);
    }

    boolean justRead = false;
    if (reader.hasNext()) {
      reader.next();
      justRead = true;
    }

    while (justRead && !(reader.isEndElement() && elementImpl.getName() != null
        && elementImpl.getName().equals(reader.getLocalName()))) {
      justRead = false;
      if (reader.isStartElement()) {
        annotationElements.add(readAnnotationElement(reader));
        if (reader.hasNext()) {
          reader.next();
          justRead = true;
        }
      } else if (reader.isCharacters()) {
        String elementText = "";
        do {
          justRead = false;
          elementText = elementText + reader.getText();
          if (reader.hasNext()) {
            reader.next();
            justRead = true;
          }
        } while (justRead && reader.isCharacters());
        elementImpl.setText(elementText);
      }
    }
    if (!annotationElements.isEmpty()) {
      elementImpl.setChildElements(annotationElements);
    }
    return elementImpl;
  }

  private List<EdmAnnotationAttribute> readAnnotationAttribute(final XMLStreamReader reader) {
    List<EdmAnnotationAttribute> annotationAttributes = new ArrayList<EdmAnnotationAttribute>();
    for (int i = 0; i < reader.getAttributeCount(); i++) {
      String attributeNamespace = reader.getAttributeNamespace(i);
      if (attributeNamespace != null && !isDefaultNamespace(attributeNamespace)
          && !mandatoryNamespaces.containsValue(attributeNamespace)
          && !edmNamespaces.contains(attributeNamespace)) {
        annotationAttributes.add(new EdmAnnotationAttributeImpl().setName(reader.getAttributeLocalName(i)).setPrefix(
            reader.getAttributePrefix(i)).setNamespace(attributeNamespace).setText(
                reader.getAttributeValue(i)));
      }
    }
    if (annotationAttributes.isEmpty()) {
      return null;
    }
    return annotationAttributes;
  }

  private boolean isDefaultNamespace(final String namespace) {
    return namespace.isEmpty();
  }

  private void checkMandatoryNamespacesAvailable() throws EntityProviderException {
    if (!xmlNamespaceMap.containsValue(Edm.NAMESPACE_EDMX_2007_06)) {
      throw new EntityProviderException(EntityProviderException.INVALID_NAMESPACE
          .addContent(Edm.NAMESPACE_EDMX_2007_06));
    } else if (!xmlNamespaceMap.containsValue(Edm.NAMESPACE_M_2007_08)) {
      throw new EntityProviderException(EntityProviderException.INVALID_NAMESPACE.addContent(Edm.NAMESPACE_M_2007_08));
    }
  }

  private void checkEdmNamespace() throws EntityProviderException {
    if (!edmNamespaces.contains(edmNamespace)) {
      throw new EntityProviderException(EntityProviderException.INVALID_NAMESPACE
          .addContent(XmlMetadataConstants.EDM_SCHEMA));
    }
  }

  private void extractNamespaces(final XMLStreamReader reader) throws EntityProviderException {
    int namespaceCount = reader.getNamespaceCount();
    for (int i = 0; i < namespaceCount; i++) {
      String namespacePrefix = reader.getNamespacePrefix(i);
      String namespaceUri = reader.getNamespaceURI(i);
      if (namespacePrefix == null || isDefaultNamespace(namespacePrefix)) {
        namespacePrefix = Edm.PREFIX_EDM;
      }
      //Ignoring the duplicate tags, parent tag namespace takes precedence
      if (!xmlNamespaceMap.containsKey(namespacePrefix)) {
        xmlNamespaceMap.put(namespacePrefix, namespaceUri);
      }
    }
  }

  private FullQualifiedName extractFQName(final String name)
      throws EntityProviderException {
    // Looking for the last dot
    String[] names = name.split("\\" + Edm.DELIMITER + "(?=[^\\" + Edm.DELIMITER + "]+$)");
    if (names.length != 2) {
      throw new EntityProviderException(EntityProviderException.ILLEGAL_ARGUMENT
          .addContent("Attribute should specify a namespace qualified name or an alias qualified name"));
    } else {
      return new FullQualifiedName(names[0], names[1]);
    }
  }

  private FullQualifiedName extractFQNameFromEntityType(final EdmEntityType entity)
      throws EntityProviderException, EdmException {
    return new FullQualifiedName(entity.getNamespace(), entity.getName());
  }

  private FullQualifiedName validateEntityTypeWithAlias(final FullQualifiedName aliasName)
      throws EntityProviderException {
    String namespace = aliasNamespaceMap.get(aliasName.getNamespace());
    FullQualifiedName fqName = new FullQualifiedName(namespace, aliasName.getName());
    if (!entityTypesMap.containsKey(fqName)) {
      throw new EntityProviderException(EntityProviderException.ILLEGAL_ARGUMENT.addContent("Invalid Type"));
    }
    return fqName;
  }

  private void validateEntityTypes() throws EntityProviderException, EdmException {
    for (Map.Entry<FullQualifiedName, EdmEntityType> entityTypes : entityTypesMap.entrySet()) {
      if (entityTypes.getValue() != null && entityTypes.getKey() != null) {
        EdmEntityTypeImpl entityType = (EdmEntityTypeImpl) entityTypes.getValue();
        if (entityType.getBaseTypeName() != null) {
          FullQualifiedName baseTypeFQName = entityType.getBaseTypeName();
          EdmEntityType baseEntityType;
          if (!entityTypesMap.containsKey(baseTypeFQName)) {
            FullQualifiedName fqName = validateEntityTypeWithAlias(baseTypeFQName);
            baseEntityType = entityTypesMap.get(fqName);
          } else {
            baseEntityType = fetchLastBaseType(baseTypeFQName, entityTypesMap);
          }
          if (baseEntityType != null && 
              (baseEntityType.getKeyProperties() == null || baseEntityType.getKeyProperties().isEmpty())) {
            throw new EntityProviderException(EntityProviderException.ILLEGAL_ARGUMENT
                .addContent("Missing key for EntityType " + baseEntityType.getName()));
          }
        } else if (entityType.getKeyProperties() == null || entityType.getKeyProperties().isEmpty()) {
          throw new EntityProviderException(EntityProviderException.ILLEGAL_ARGUMENT
              .addContent("Missing key for EntityType " + entityType.getName()));
        }
      }
    }
  }

  /*
   * This method gets the last base type of the EntityType
   * which has key defined in order to validate it
   */
  private EdmEntityType fetchLastBaseType(FullQualifiedName baseTypeFQName,
      Map<FullQualifiedName, EdmEntityType> entityTypesMap2)
      throws EntityProviderException, EdmException {

    EdmEntityTypeImpl baseEntityType = null;
    while (baseTypeFQName != null) {
      baseEntityType = (EdmEntityTypeImpl) entityTypesMap2.get(baseTypeFQName);
      if(baseEntityType != null){
        if (baseEntityType.getKeyPropertyNames() != null && !baseEntityType.getKeyPropertyNames().isEmpty()) {
          break;
        } else if (baseEntityType.getBaseType() != null) {
          baseTypeFQName = baseEntityType.getBaseTypeName();
        } else if (baseEntityType.getBaseType() == null) {
          break;
        }
      }
    }
    return baseEntityType;
  }

  private FullQualifiedName validateComplexTypeWithAlias(final FullQualifiedName aliasName)
      throws EntityProviderException {
    String namespace = aliasNamespaceMap.get(aliasName.getNamespace());
    FullQualifiedName fqName = new FullQualifiedName(namespace, aliasName.getName());
    if (!complexTypesMap.containsKey(fqName)) {
      throw new EntityProviderException(EntityProviderException.ILLEGAL_ARGUMENT.addContent("Invalid BaseType")
          .addContent(fqName));
    }
    return fqName;
  }

  private void validateComplexTypes() throws EntityProviderException, EdmException {
    for (Map.Entry<FullQualifiedName, EdmComplexType> complexTypes : complexTypesMap.entrySet()) {
      if (complexTypes.getValue() != null && complexTypes.getKey() != null) {
        EdmComplexTypeImpl complexType = (EdmComplexTypeImpl) complexTypes.getValue();
        if (complexType.getBaseType() != null) {
          FullQualifiedName baseTypeFQName = complexType.getEdmBaseTypeName();
          if (!complexTypesMap.containsKey(baseTypeFQName)) {
            validateComplexTypeWithAlias(baseTypeFQName);
          }
        }
      }
    }
  }

  private void validateRelationship() throws EntityProviderException, EdmException {
    for (EdmNavigationProperty navProperty : navProperties) {
      EdmNavigationPropertyImpl navigationImpl = (EdmNavigationPropertyImpl) navProperty;
      if (associationsMap.containsKey(navigationImpl.getRelationshipName())) {
        EdmAssociationImpl assoc = (EdmAssociationImpl) associationsMap.get(navigationImpl.getRelationshipName());
        if (!(assoc.getEnd1().getRole().equals(navProperty.getFromRole())
            ^ assoc.getEnd1().getRole().equals(navProperty.getToRole())
            && (assoc.getEnd2().getRole().equals(navProperty.getFromRole()) ^ assoc.getEnd2().getRole().equals(
                navProperty.getToRole())))) {
          throw new EntityProviderException(EntityProviderException.ILLEGAL_ARGUMENT
              .addContent("Invalid end of association"));
        }
        if (!entityTypesMap.containsKey(extractFQNameFromEntityType(assoc.getEnd1().getEntityType()))) {
          EdmEntityType entityType = assoc.getEnd1().getEntityType();
          validateEntityTypeWithAlias(extractFQNameFromEntityType(entityType));
        }
        if (!entityTypesMap.containsKey(extractFQNameFromEntityType(assoc.getEnd2().getEntityType()))) {
          EdmEntityType entityType = assoc.getEnd2().getEntityType();
          validateEntityTypeWithAlias(extractFQNameFromEntityType(entityType));
        }
      } else {
        throw new EntityProviderException(EntityProviderException.ILLEGAL_ARGUMENT.addContent("Invalid Relationship"));
      }
    }

  }

  private void validateAssociation() throws EntityProviderException, EdmException {
    for (Map.Entry<FullQualifiedName, EdmEntityContainer> container : containerMap.entrySet()) {
      for (EdmAssociationSet associationSet : container.getValue().getAssociationSets()) {
        FullQualifiedName association = new FullQualifiedName(associationSet.getAssociation().getNamespace(),
            associationSet.getAssociation().getName());
        if (associationsMap.containsKey(association)) {
          validateAssociationEnd(((EdmAssociationSetImpl) associationSet).getEnd1(), associationsMap.get(association));
          validateAssociationEnd(((EdmAssociationSetImpl) associationSet).getEnd2(), associationsMap.get(association));
          boolean end1 = false;
          boolean end2 = false;
          for (EdmEntitySet entitySet : container.getValue().getEntitySets()) {
            EdmAssociationSetEnd associationSetEnd1 = ((EdmAssociationSetImpl) associationSet).getEnd1();
            if (entitySet.getName().equals(((EdmAssociationSetEndImpl) associationSetEnd1).getEntitySetName())) {
              end1 = true;
            }
            EdmAssociationSetEnd associationSetEnd2 = ((EdmAssociationSetImpl) associationSet).getEnd2();
            if (entitySet.getName().equals(((EdmAssociationSetEndImpl) associationSetEnd2).getEntitySetName())) {
              end2 = true;
            }
          }
          if (!(end1 && end2)) {
            throw new EntityProviderException(EntityProviderException.ILLEGAL_ARGUMENT
                .addContent("Invalid AssociationSet"));
          }
        } else {
          throw new EntityProviderException(EntityProviderException.ILLEGAL_ARGUMENT
              .addContent("Invalid AssociationSet"));
        }
      }
    }

  }

  private void validateAssociationEnd(final EdmAssociationSetEnd end, final EdmAssociation association)
      throws EntityProviderException, EdmException {
    if (!(association.getEnd1().getRole().equals(end.getRole()) ^ association
        .getEnd2().getRole().equals(end.getRole()))) {
      throw new EntityProviderException(EntityProviderException.COMMON.addContent("Invalid Association"));
    }
  }

  private void validateEntitySet() throws EntityProviderException, EdmException {
    for (Map.Entry<FullQualifiedName, EdmEntityContainer> container : containerMap.entrySet()) {
      for (EdmEntitySet entitySet : container.getValue().getEntitySets()) {
        FullQualifiedName entityType = extractFQNameFromEntityType(entitySet.getEntityType());
        if (!(entityTypesMap.containsKey(entityType))) {
          validateEntityTypeWithAlias(entityType);
        }
      }
    }
  }

  private void validateFunctionImport() throws EntityProviderException, EdmException {
    for (EdmFunctionImport functionImport : edmFunctionImportList) {
      EdmTyped returnType = functionImport.getReturnType();
      if (returnType != null) {
        FullQualifiedName fqn = extractFQName(returnType.toString());
        String entitySet = ((EdmFunctionImportImpl)functionImport).getEntitySetName();
        if (returnType.getMultiplicity() == EdmMultiplicity.MANY && entitySet == null && entityTypesMap.get(
            fqn) != null) {
          throw new EntityProviderException(EntityProviderException.MISSING_ATTRIBUTE.addContent("EntitySet = "
              + entitySet, XmlMetadataConstants.EDM_FUNCTION_IMPORT + " = " + functionImport.getName()));
        } else if (returnType.getMultiplicity() != EdmMultiplicity.MANY && entitySet != null && entityTypesMap.get(
            fqn) == null) {
          throw new EntityProviderException(EntityProviderException.INVALID_ATTRIBUTE.addContent("EntitySet = "
              + entitySet, XmlMetadataConstants.EDM_FUNCTION_IMPORT
                  + " = " + functionImport.getName()));
        }
      }
    }
  }

  
  private void validate() throws EntityProviderException, EdmException {
    checkMandatoryNamespacesAvailable();
    validateEntityTypes();
    validateComplexTypes();
    validateRelationship();
    validateEntitySet();
    validateAssociation();
    validateFunctionImport();
  }

  private void initialize() {
    xmlNamespaceMap = new HashMap<String, String>();
    mandatoryNamespaces = new HashMap<String, String>();
    mandatoryNamespaces.put(Edm.PREFIX_EDMX, Edm.NAMESPACE_EDMX_2007_06);
    mandatoryNamespaces.put(Edm.PREFIX_M, Edm.NAMESPACE_M_2007_08);
    edmNamespaces = new HashSet<String>();
    edmNamespaces.add(Edm.NAMESPACE_EDM_2006_04);
    edmNamespaces.add(Edm.NAMESPACE_EDM_2007_05);
    edmNamespaces.add(Edm.NAMESPACE_EDM_2008_01);
    edmNamespaces.add(Edm.NAMESPACE_EDM_2008_09);

  }
}
