class XmlMetadataProducer {

  public static void writeMetadata(final DataServices metadata, final XMLStreamWriter xmlStreamWriter,
      Map<String, String> predefinedNamespaces) throws EntityProviderException {

    try {
      String edmxNamespace = Edm.NAMESPACE_EDMX_2007_06;
      String defaultNamespace = Edm.NAMESPACE_EDM_2008_09;

      if (predefinedNamespaces == null) {
        predefinedNamespaces = new HashMap<String, String>();
      } else {
        String predefinedEdmxNamespace = predefinedNamespaces.get(Edm.PREFIX_EDMX);
        if (predefinedEdmxNamespace != null) {
          edmxNamespace = predefinedEdmxNamespace;
          predefinedNamespaces.remove(Edm.PREFIX_EDMX);
        }
        String predefinedDefaultNamespace = predefinedNamespaces.get(null);
        if (predefinedDefaultNamespace != null) {
          defaultNamespace = predefinedDefaultNamespace;
          predefinedNamespaces.remove(null);
        }
      }

      xmlStreamWriter.writeStartDocument();
      xmlStreamWriter.setPrefix(Edm.PREFIX_EDMX, edmxNamespace);
      xmlStreamWriter.setPrefix(Edm.PREFIX_M, Edm.NAMESPACE_M_2007_08);

      xmlStreamWriter.writeStartElement(edmxNamespace, "Edmx");
      xmlStreamWriter.writeNamespace(Edm.PREFIX_EDMX, edmxNamespace);
      if(metadata.getCustomEdmxVersion() == null){
        xmlStreamWriter.writeAttribute("Version", "1.0");
      }else {
        xmlStreamWriter.writeAttribute("Version", metadata.getCustomEdmxVersion());
      }

      for (Map.Entry<String, String> entry : predefinedNamespaces.entrySet()) {
        xmlStreamWriter.writeNamespace(entry.getKey(), entry.getValue());
      }

      writeAnnotationElements(metadata.getAnnotationElements(), predefinedNamespaces, xmlStreamWriter);

      xmlStreamWriter.writeStartElement(edmxNamespace, XmlMetadataConstants.EDM_DATA_SERVICES);
      xmlStreamWriter.setDefaultNamespace(defaultNamespace);
      xmlStreamWriter.writeAttribute(Edm.PREFIX_M, Edm.NAMESPACE_M_2007_08,
          XmlMetadataConstants.EDM_DATA_SERVICE_VERSION, metadata.getDataServiceVersion());
      xmlStreamWriter.writeNamespace(Edm.PREFIX_M, Edm.NAMESPACE_M_2007_08);

      Collection<Schema> schemas = metadata.getSchemas();
      if (schemas != null) {
        for (Schema schema : schemas) {
          xmlStreamWriter.writeStartElement(XmlMetadataConstants.EDM_SCHEMA);
          if (schema.getAlias() != null) {
            xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_SCHEMA_ALIAS, schema.getAlias());
          }
          xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_SCHEMA_NAMESPACE, schema.getNamespace());
          xmlStreamWriter.writeDefaultNamespace(defaultNamespace);

          writeAnnotationAttributes(schema.getAnnotationAttributes(), predefinedNamespaces, null, xmlStreamWriter);

          Collection<Using> usings = schema.getUsings();
          if (usings != null) {
            for (Using using : usings) {
              xmlStreamWriter.writeStartElement(XmlMetadataConstants.EDM_USING);
              xmlStreamWriter.writeAttribute("Namespace", using.getNamespace());
              xmlStreamWriter.writeAttribute("Alias", using.getAlias());
              writeAnnotationAttributes(using.getAnnotationAttributes(), predefinedNamespaces, null, xmlStreamWriter);
              writeDocumentation(using.getDocumentation(), predefinedNamespaces, xmlStreamWriter);
              writeAnnotationElements(using.getAnnotationElements(), predefinedNamespaces, xmlStreamWriter);
              xmlStreamWriter.writeEndElement();
            }
          }

          Collection<EntityType> entityTypes = schema.getEntityTypes();
          if (entityTypes != null) {
            for (EntityType entityType : entityTypes) {
              xmlStreamWriter.writeStartElement(XmlMetadataConstants.EDM_ENTITY_TYPE);
              xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_NAME, entityType.getName());
              if (entityType.getBaseType() != null) {
                xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_BASE_TYPE, entityType.getBaseType().toString());
              }
              if (entityType.isAbstract()) {
                xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_TYPE_ABSTRACT, "true");
              }
              if (entityType.isHasStream()) {
                xmlStreamWriter.writeAttribute(Edm.PREFIX_M, Edm.NAMESPACE_M_2007_08,
                    XmlMetadataConstants.M_ENTITY_TYPE_HAS_STREAM, "true");
              }

              writeCustomizableFeedMappings(entityType.getCustomizableFeedMappings(), xmlStreamWriter);

              writeAnnotationAttributes(entityType.getAnnotationAttributes(), predefinedNamespaces, null,
                  xmlStreamWriter);

              writeDocumentation(entityType.getDocumentation(), predefinedNamespaces, xmlStreamWriter);

              Key key = entityType.getKey();
              if (key != null) {
                xmlStreamWriter.writeStartElement(XmlMetadataConstants.EDM_ENTITY_TYPE_KEY);

                writeAnnotationAttributes(key.getAnnotationAttributes(), predefinedNamespaces, null, xmlStreamWriter);

                Collection<PropertyRef> propertyRefs = entityType.getKey().getKeys();
                for (PropertyRef propertyRef : propertyRefs) {
                  xmlStreamWriter.writeStartElement(XmlMetadataConstants.EDM_PROPERTY_REF);

                  writeAnnotationAttributes(propertyRef.getAnnotationAttributes(), predefinedNamespaces, null,
                      xmlStreamWriter);

                  xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_NAME, propertyRef.getName());

                  writeAnnotationElements(propertyRef.getAnnotationElements(), predefinedNamespaces, xmlStreamWriter);

                  xmlStreamWriter.writeEndElement();
                }

                writeAnnotationElements(key.getAnnotationElements(), predefinedNamespaces, xmlStreamWriter);

                xmlStreamWriter.writeEndElement();
              }

              Collection<Property> properties = entityType.getProperties();
              if (properties != null) {
                writeProperties(properties, predefinedNamespaces, xmlStreamWriter);
              }

              Collection<NavigationProperty> navigationProperties = entityType.getNavigationProperties();
              if (navigationProperties != null) {
                for (NavigationProperty navigationProperty : navigationProperties) {
                  xmlStreamWriter.writeStartElement(XmlMetadataConstants.EDM_NAVIGATION_PROPERTY);
                  xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_NAME, navigationProperty.getName());
                  xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_NAVIGATION_RELATIONSHIP, navigationProperty
                      .getRelationship().toString());
                  xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_NAVIGATION_FROM_ROLE, navigationProperty
                      .getFromRole());
                  xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_NAVIGATION_TO_ROLE, navigationProperty
                      .getToRole());

                  writeAnnotationAttributes(navigationProperty.getAnnotationAttributes(), predefinedNamespaces, null,
                      xmlStreamWriter);

                  writeDocumentation(navigationProperty.getDocumentation(), predefinedNamespaces, xmlStreamWriter);

                  writeAnnotationElements(navigationProperty.getAnnotationElements(), predefinedNamespaces,
                      xmlStreamWriter);

                  xmlStreamWriter.writeEndElement();
                }
              }

              writeAnnotationElements(entityType.getAnnotationElements(), predefinedNamespaces, xmlStreamWriter);

              xmlStreamWriter.writeEndElement();
            }
          }

          Collection<ComplexType> complexTypes = schema.getComplexTypes();
          if (complexTypes != null) {
            for (ComplexType complexType : complexTypes) {
              xmlStreamWriter.writeStartElement(XmlMetadataConstants.EDM_COMPLEX_TYPE);
              xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_NAME, complexType.getName());
              if (complexType.getBaseType() != null) {
                xmlStreamWriter
                    .writeAttribute(XmlMetadataConstants.EDM_BASE_TYPE, complexType.getBaseType().toString());
              }
              if (complexType.isAbstract()) {
                xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_TYPE_ABSTRACT, "true");
              }

              writeAnnotationAttributes(complexType.getAnnotationAttributes(), predefinedNamespaces, null,
                  xmlStreamWriter);

              writeDocumentation(complexType.getDocumentation(), predefinedNamespaces, xmlStreamWriter);

              Collection<Property> properties = complexType.getProperties();
              if (properties != null) {
                writeProperties(properties, predefinedNamespaces, xmlStreamWriter);
              }

              writeAnnotationElements(complexType.getAnnotationElements(), predefinedNamespaces, xmlStreamWriter);

              xmlStreamWriter.writeEndElement();
            }
          }

          Collection<Association> associations = schema.getAssociations();
          if (associations != null) {
            for (Association association : associations) {
              xmlStreamWriter.writeStartElement(XmlMetadataConstants.EDM_ASSOCIATION);
              xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_NAME, association.getName());

              writeAnnotationAttributes(association.getAnnotationAttributes(), predefinedNamespaces, null,
                  xmlStreamWriter);

              writeDocumentation(association.getDocumentation(), predefinedNamespaces, xmlStreamWriter);

              writeAssociationEnd(association.getEnd1(), predefinedNamespaces, xmlStreamWriter);
              writeAssociationEnd(association.getEnd2(), predefinedNamespaces, xmlStreamWriter);

              ReferentialConstraint referentialConstraint = association.getReferentialConstraint();
              if (referentialConstraint != null) {
                xmlStreamWriter.writeStartElement(XmlMetadataConstants.EDM_ASSOCIATION_CONSTRAINT);
                writeAnnotationAttributes(referentialConstraint.getAnnotationAttributes(), predefinedNamespaces, null,
                    xmlStreamWriter);
                writeDocumentation(referentialConstraint.getDocumentation(), predefinedNamespaces, xmlStreamWriter);

                ReferentialConstraintRole principal = referentialConstraint.getPrincipal();
                xmlStreamWriter.writeStartElement(XmlMetadataConstants.EDM_ASSOCIATION_PRINCIPAL);
                xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_ROLE, principal.getRole());
                writeAnnotationAttributes(principal.getAnnotationAttributes(), predefinedNamespaces, null,
                    xmlStreamWriter);

                for (PropertyRef propertyRef : principal.getPropertyRefs()) {
                  xmlStreamWriter.writeStartElement(XmlMetadataConstants.EDM_PROPERTY_REF);
                  xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_NAME, propertyRef.getName());
                  xmlStreamWriter.writeEndElement();
                }
                writeAnnotationElements(principal.getAnnotationElements(), predefinedNamespaces, xmlStreamWriter);
                xmlStreamWriter.writeEndElement();

                ReferentialConstraintRole dependent = referentialConstraint.getDependent();
                xmlStreamWriter.writeStartElement(XmlMetadataConstants.EDM_ASSOCIATION_DEPENDENT);
                xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_ROLE, dependent.getRole());
                writeAnnotationAttributes(dependent.getAnnotationAttributes(), predefinedNamespaces, null,
                    xmlStreamWriter);

                for (PropertyRef propertyRef : dependent.getPropertyRefs()) {
                  xmlStreamWriter.writeStartElement(XmlMetadataConstants.EDM_PROPERTY_REF);
                  xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_NAME, propertyRef.getName());
                  xmlStreamWriter.writeEndElement();
                }
                writeAnnotationElements(dependent.getAnnotationElements(), predefinedNamespaces, xmlStreamWriter);
                xmlStreamWriter.writeEndElement();

                writeAnnotationElements(referentialConstraint.getAnnotationElements(), predefinedNamespaces,
                    xmlStreamWriter);
                xmlStreamWriter.writeEndElement();
              }

              writeAnnotationElements(association.getAnnotationElements(), predefinedNamespaces, xmlStreamWriter);

              xmlStreamWriter.writeEndElement();
            }
          }

          Collection<EntityContainer> entityContainers = schema.getEntityContainers();
          if (entityContainers != null) {
            for (EntityContainer entityContainer : entityContainers) {
              xmlStreamWriter.writeStartElement(XmlMetadataConstants.EDM_ENTITY_CONTAINER);
              xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_NAME, entityContainer.getName());
              if (entityContainer.getExtendz() != null) {
                xmlStreamWriter
                    .writeAttribute(XmlMetadataConstants.EDM_CONTAINER_EXTENDZ, entityContainer.getExtendz());
              }
              if (entityContainer.isDefaultEntityContainer()) {
                xmlStreamWriter.writeAttribute(Edm.PREFIX_M, Edm.NAMESPACE_M_2007_08,
                    XmlMetadataConstants.EDM_CONTAINER_IS_DEFAULT, "true");
              }

              writeAnnotationAttributes(entityContainer.getAnnotationAttributes(), predefinedNamespaces, null,
                  xmlStreamWriter);

              writeDocumentation(entityContainer.getDocumentation(), predefinedNamespaces, xmlStreamWriter);

              Collection<EntitySet> entitySets = entityContainer.getEntitySets();
              if (entitySets != null) {
                for (EntitySet entitySet : entitySets) {
                  xmlStreamWriter.writeStartElement(XmlMetadataConstants.EDM_ENTITY_SET);
                  xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_NAME, entitySet.getName());
                  xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_ENTITY_TYPE, entitySet.getEntityType()
                      .toString());

                  writeAnnotationAttributes(entitySet.getAnnotationAttributes(), predefinedNamespaces, null,
                      xmlStreamWriter);

                  writeDocumentation(entitySet.getDocumentation(), predefinedNamespaces, xmlStreamWriter);

                  writeAnnotationElements(entitySet.getAnnotationElements(), predefinedNamespaces, xmlStreamWriter);

                  xmlStreamWriter.writeEndElement();
                }
              }

              Collection<AssociationSet> associationSets = entityContainer.getAssociationSets();
              if (associationSets != null) {
                for (AssociationSet associationSet : associationSets) {
                  xmlStreamWriter.writeStartElement(XmlMetadataConstants.EDM_ASSOCIATION_SET);
                  xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_NAME, associationSet.getName());
                  xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_ASSOCIATION, associationSet.getAssociation()
                      .toString());

                  writeAnnotationAttributes(associationSet.getAnnotationAttributes(), predefinedNamespaces, null,
                      xmlStreamWriter);

                  writeDocumentation(associationSet.getDocumentation(), predefinedNamespaces, xmlStreamWriter);

                  writeAssociationSetEnd(associationSet.getEnd1(), predefinedNamespaces, xmlStreamWriter);
                  writeAssociationSetEnd(associationSet.getEnd2(), predefinedNamespaces, xmlStreamWriter);

                  writeAnnotationElements(associationSet.getAnnotationElements(), predefinedNamespaces,
                      xmlStreamWriter);

                  xmlStreamWriter.writeEndElement();
                }
              }

              Collection<FunctionImport> functionImports = entityContainer.getFunctionImports();
              if (functionImports != null) {
                for (FunctionImport functionImport : functionImports) {
                  xmlStreamWriter.writeStartElement(XmlMetadataConstants.EDM_FUNCTION_IMPORT);
                  xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_NAME, functionImport.getName());
                  if (functionImport.getReturnType() != null) {
                    xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_FUNCTION_IMPORT_RETURN, functionImport
                        .getReturnType().toString());
                  }
                  if (functionImport.getEntitySet() != null) {
                    xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_ENTITY_SET, functionImport.getEntitySet());
                  }
                  if (functionImport.getHttpMethod() != null) {
                    xmlStreamWriter.writeAttribute(Edm.PREFIX_M, Edm.NAMESPACE_M_2007_08,
                        XmlMetadataConstants.EDM_FUNCTION_IMPORT_HTTP_METHOD, functionImport.getHttpMethod());
                  }

                  writeAnnotationAttributes(functionImport.getAnnotationAttributes(), predefinedNamespaces, null,
                      xmlStreamWriter);

                  writeDocumentation(functionImport.getDocumentation(), predefinedNamespaces, xmlStreamWriter);

                  Collection<FunctionImportParameter> functionImportParameters = functionImport.getParameters();
                  if (functionImportParameters != null) {
                    for (FunctionImportParameter functionImportParameter : functionImportParameters) {
                      xmlStreamWriter.writeStartElement(XmlMetadataConstants.EDM_FUNCTION_PARAMETER);
                      xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_NAME, functionImportParameter.getName());
                      xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_TYPE, functionImportParameter.getType()
                          .getFullQualifiedName().toString());
                      if (functionImportParameter.getMode() != null) {
                        xmlStreamWriter.writeAttribute(XmlMetadataConstants.EDM_FUNCTION_PARAMETER_MODE,
                            functionImportParameter.getMode());
                      }

                      writeFacets(xmlStreamWriter, functionImportParameter.getFacets());

                      writeAnnotationAttributes(functionImportParameter.getAnnotationAttributes(),
                          predefinedNamespaces, null, xmlStreamWriter);

                      writeDocumentation(functionImportParameter.getDocumentation(), predefinedNamespaces,
                          xmlStreamWriter);

                      writeAnnotationElements(functionImportParameter.getAnnotationElements(), predefinedNamespaces,
                          xmlStreamWriter);

                      xmlStreamWriter.writeEndElement();
                    }
                  }

                  writeAnnotationElements(functionImport.getAnnotationElements(), predefinedNamespaces,
                      xmlStreamWriter);

                  xmlStreamWriter.writeEndElement();
                }
              }

              writeAnnotationElements(entityContainer.getAnnotationElements(), predefinedNamespaces, xmlStreamWriter);

              xmlStreamWriter.writeEndElement();
            }
          }

          writeAnnotationElements(schema.getAnnotationElements(), predefinedNamespaces, xmlStreamWriter);

          xmlStreamWriter.writeEndElement();
        }
      }

      xmlStreamWriter.writeEndElement();
      xmlStreamWriter.writeEndElement();
      xmlStreamWriter.writeEndDocument();

      xmlStreamWriter.flush();
    } catch (XMLStreamException e) {
      throw new EntityProviderProducerException(EntityProviderException.COMMON, e);
    } catch (FactoryConfigurationError e) {
      throw new EntityProviderProducerException(EntityProviderException.COMMON, e);
    }
  }

}