package org.odata4j.format.xml; //import static org.odata4j.format.xml.XmlFormatParser.NS_EDM2008_9; import static org.odata4j.format.xml.XmlFormatParser.NS_EDM2009_11; import static org.odata4j.format.xml.XmlFormatParser.NS_EDMANNOTATION; import java.io.Writer; import org.odata4j.core.NamespacedAnnotation; import org.odata4j.core.PrefixedNamespace; import org.odata4j.edm.EdmAnnotationAttribute; import org.odata4j.edm.EdmAnnotationElement; import org.odata4j.edm.EdmAssociation; import org.odata4j.edm.EdmAssociationSet; import org.odata4j.edm.EdmComplexType; import org.odata4j.edm.EdmDataServices; import org.odata4j.edm.EdmDocumentation; import org.odata4j.edm.EdmEntityContainer; import org.odata4j.edm.EdmEntitySet; import org.odata4j.edm.EdmEntityType; import org.odata4j.edm.EdmFunctionImport; import org.odata4j.edm.EdmFunctionImport.FunctionKind; import org.odata4j.edm.EdmFunctionParameter; import org.odata4j.edm.EdmItem; import org.odata4j.edm.EdmNavigationProperty; import org.odata4j.edm.EdmProperty; import org.odata4j.edm.EdmProperty.CollectionKind; import org.odata4j.edm.EdmSchema; import org.odata4j.stax2.QName2; import org.odata4j.stax2.XMLFactoryProvider2; import org.odata4j.stax2.XMLWriter2; public class EdmxFormatWriter extends XmlFormatWriter { public static void write(EdmDataServices services, Writer w) { XMLWriter2 writer = XMLFactoryProvider2.getInstance().newXMLWriterFactory2().createXMLWriter(w); writer.startDocument(); writer.startElement(new QName2(edmx, "Edmx", "edmx")); writer.writeAttribute("Version", "1.0"); writer.writeNamespace("edmx", edmx); //writer.writeNamespace("d", d); writeExtensionNamespaces(services, writer); writer.startElement(new QName2(edmx, "DataServices", "edmx")); writer.writeAttribute(new QName2(m, "DataServiceVersion", "m"), "1.0"); writer.writeAttribute(new QName2(m, "MaxDataServiceVersion", "m"), "3.0"); writer.writeNamespace("m", m); // Schema for (EdmSchema schema : services.getSchemas()) { writer.startElement(new QName2("Schema")); writer.writeAttribute("Namespace", schema.getNamespace()); writer.writeNamespace("xmlns", NS_EDM2009_11); writeAnnotationAttributes(schema, writer); writeDocumentation(schema, writer); // EntityType for (EdmEntityType eet : schema.getEntityTypes()) { writer.startElement(new QName2("EntityType")); if (eet.getOpenType() != null) { writer.writeAttribute("OpenType", eet.getOpenType().toString()); } writer.writeAttribute("Name", eet.getName()); if (eet.getIsAbstract() != null) { writer.writeAttribute("Abstract", eet.getIsAbstract().toString()); } if (Boolean.TRUE.equals(eet.getHasStream())) { writer.writeAttribute(new QName2(m, "HasStream", "m"), "true"); } // keys only on base types if (eet.isRootType()) { writeAnnotationAttributes(eet, writer); writeDocumentation(eet, writer); writer.startElement(new QName2("Key")); for (String key : eet.getKeys()) { writer.startElement(new QName2("PropertyRef")); writer.writeAttribute("Name", key); writer.endElement("PropertyRef"); } writer.endElement("Key"); } else { writer.writeAttribute("BaseType", eet.getBaseType().getFullyQualifiedTypeName()); writeAnnotationAttributes(eet, writer); writeDocumentation(eet, writer); } writeProperties(eet.getDeclaredProperties(), writer); for (EdmNavigationProperty np : eet.getDeclaredNavigationProperties()) { writer.startElement(new QName2("NavigationProperty")); writer.writeAttribute("Name", np.getName()); writer.writeAttribute("Relationship", np.getRelationship().getFQNamespaceName()); writer.writeAttribute("FromRole", np.getFromRole().getRole()); writer.writeAttribute("ToRole", np.getToRole().getRole()); // Containment Navigation property if (np.getCotainsTarget()) { writer.writeAttribute("ContainsTarget", Boolean.TRUE.toString()); } writeAnnotationAttributes(np, writer); writeDocumentation(np, writer); writeAnnotationElements(np, writer); writer.endElement("NavigationProperty"); } writeAnnotationElements(eet, writer); writer.endElement("EntityType"); } // ComplexType for (EdmComplexType ect : schema.getComplexTypes()) { writer.startElement(new QName2("ComplexType")); writer.writeAttribute("Name", ect.getName()); if (ect.getBaseType() != null) { writer.writeAttribute("BaseType", ect.getBaseType().getFullyQualifiedTypeName()); } if (ect.getIsAbstract() != null) { writer.writeAttribute("Abstract", ect.getIsAbstract().toString()); } writeAnnotationAttributes(ect, writer); writeDocumentation(ect, writer); writeProperties(ect.getProperties(), writer); writeAnnotationElements(ect, writer); writer.endElement("ComplexType"); } // Association for (EdmAssociation assoc : schema.getAssociations()) { writer.startElement(new QName2("Association")); writer.writeAttribute("Name", assoc.getName()); writeAnnotationAttributes(assoc, writer); writeDocumentation(assoc, writer); writer.startElement(new QName2("End")); writer.writeAttribute("Type", assoc.getEnd1().getType().getFullyQualifiedTypeName()); writer.writeAttribute("Multiplicity", assoc.getEnd1().getMultiplicity().getSymbolString()); writer.writeAttribute("Role", assoc.getEnd1().getRole()); if (assoc.getEnd1().getOnDeleteAction() != null) { writer.startElement(new QName2("OnDelete")); writer.writeAttribute("Action", assoc.getEnd1().getOnDeleteAction().getSymbolString()); writer.endElement("OnDelete"); } writeAnnotationAttributes(assoc.getEnd1(), writer); writeAnnotationElements(assoc.getEnd1(), writer); writer.endElement("End"); writer.startElement(new QName2("End")); writer.writeAttribute("Type", assoc.getEnd2().getType().getFullyQualifiedTypeName()); writer.writeAttribute("Multiplicity", assoc.getEnd2().getMultiplicity().getSymbolString()); writer.writeAttribute("Role", assoc.getEnd2().getRole()); if (assoc.getEnd2().getOnDeleteAction() != null) { writer.startElement(new QName2("OnDelete")); writer.writeAttribute("Action", assoc.getEnd2().getOnDeleteAction().getSymbolString()); writer.endElement("OnDelete"); } writeAnnotationAttributes(assoc.getEnd2(), writer); writeAnnotationElements(assoc.getEnd2(), writer); writer.endElement("End"); if (assoc.getRefConstraint() != null && assoc.getRefConstraint().getPrincipalRole() != null && assoc.getRefConstraint().getDependentRole() != null) { writer.startElement(new QName2("ReferentialConstraint")); writer.startElement(new QName2("Principal")); writer.writeAttribute("Role", assoc.getRefConstraint().getPrincipalRole()); for (String reference : assoc.getRefConstraint().getPrincipalReferences()) { writer.startElement(new QName2("PropertyRef")); writer.writeAttribute("Name", reference); writer.endElement("PropertyRef"); } writer.endElement("Principal"); writer.startElement(new QName2("Dependent")); writer.writeAttribute("Role", assoc.getRefConstraint().getDependentRole()); for (String reference : assoc.getRefConstraint().getDependentReferences()) { writer.startElement(new QName2("PropertyRef")); writer.writeAttribute("Name", reference); writer.endElement("PropertyRef"); } writer.endElement("Dependent"); writer.endElement("ReferetialConstraint"); } writeAnnotationElements(assoc, writer); writer.endElement("Association"); } // EntityContainer for (EdmEntityContainer container : schema.getEntityContainers()) { writer.startElement(new QName2("EntityContainer")); writer.writeAttribute("Name", container.getName()); writer.writeAttribute(new QName2(m, "IsDefaultEntityContainer", "m"), Boolean.toString(container.isDefault())); if (container.getLazyLoadingEnabled() != null) { writer.writeAttribute(new QName2(NS_EDMANNOTATION, "LazyLoadingEnabled", "annotation") , Boolean.toString(container.getLazyLoadingEnabled())); writer.writeNamespace("annotation", NS_EDMANNOTATION); } if (container.getExtendz() != null) { writer.writeAttribute("Extends", container.getExtendz()); } writeAnnotationAttributes(container, writer); writeDocumentation(container, writer); for (EdmEntitySet ees : container.getEntitySets()) { writer.startElement(new QName2("EntitySet")); writer.writeAttribute("Name", ees.getName()); writer.writeAttribute("EntityType", ees.getType().getFullyQualifiedTypeName()); writeAnnotationAttributes(ees, writer); writeDocumentation(ees, writer); writeAnnotationElements(ees, writer); writer.endElement("EntitySet"); } for (EdmAssociationSet eas : container.getAssociationSets()) { writer.startElement(new QName2("AssociationSet")); writer.writeAttribute("Name", eas.getName()); writer.writeAttribute("Association", eas.getAssociation().getFQNamespaceName()); writeAnnotationAttributes(eas, writer); writeDocumentation(eas, writer); writer.startElement(new QName2("End")); writer.writeAttribute("EntitySet", eas.getEnd1().getEntitySet().getName()); writer.writeAttribute("Role", eas.getEnd1().getRole().getRole()); writeAnnotationAttributes(eas.getEnd1(), writer); writeDocumentation(eas.getEnd1(), writer); writeAnnotationElements(eas.getEnd1(), writer); writer.endElement("End"); writer.startElement(new QName2("End")); writer.writeAttribute("EntitySet", eas.getEnd2().getEntitySet().getName()); writer.writeAttribute("Role", eas.getEnd2().getRole().getRole()); writeAnnotationAttributes(eas.getEnd2(), writer); writeDocumentation(eas.getEnd2(), writer); writeAnnotationElements(eas.getEnd2(), writer); writer.endElement("End"); writeAnnotationElements(eas, writer); writer.endElement("AssociationSet"); } for (EdmFunctionImport fi : container.getFunctionImports()) { writer.startElement(new QName2("FunctionImport")); writer.writeAttribute("Name", fi.getName()); if (fi.getReturnType() != null) { // TODO: how to differentiate inline ReturnType vs embedded ReturnType? writer.writeAttribute("ReturnType", fi.getReturnType().getFullyQualifiedTypeName()); } if (fi.getEntitySet() != null) { writer.writeAttribute("EntitySet", fi.getEntitySet().getName()); } if (fi.getFunctionKind().equals(FunctionKind.ServiceOperation)){ writer.writeAttribute(new QName2(m, "HttpMethod", "m"), fi.getHttpMethod()); } else { writer.writeAttribute("IsBindable", Boolean.toString(fi.isBindable())); writer.writeAttribute("IsSideEffecting", Boolean.toString(fi.isSideEffecting())); writer.writeAttribute(new QName2(m, "IsAlwaysBindable", "m"), Boolean.toString(fi.isAlwaysBindable())); } writeAnnotationAttributes(fi, writer); writeDocumentation(fi, writer); for (EdmFunctionParameter param : fi.getParameters()) { writer.startElement(new QName2("Parameter")); writer.writeAttribute("Name", param.getName()); writer.writeAttribute("Type", param.getType().getFullyQualifiedTypeName()); if (param.getMode() != null) writer.writeAttribute("Mode", param.getMode().toString()); if (param.isNullable() != null) writer.writeAttribute("Nullable", param.isNullable().toString()); if (param.getMaxLength() != null) { writer.writeAttribute("MaxLength", param.getMaxLength().toString()); } if (param.getPrecision() != null) { writer.writeAttribute("Precision", param.getPrecision().toString()); } if (param.getScale() != null) { writer.writeAttribute("Scale", param.getScale().toString()); } writeAnnotationAttributes(param, writer); writeDocumentation(param, writer); writeAnnotationElements(param, writer); writer.endElement("Parameter"); } writeAnnotationElements(fi, writer); writer.endElement("FunctionImport"); } writeAnnotationElements(container, writer); writer.endElement("EntityContainer"); } writeAnnotationElements(schema, writer); writer.endElement("Schema"); } writer.endElement("DataServices"); writer.endElement("Edmx"); writer.endDocument(); } /** * Extensions to CSDL like Annotations appear in an application specific set * of namespaces. */ private static void writeExtensionNamespaces(EdmDataServices services, XMLWriter2 writer) { if (services.getNamespaces() != null) { for (PrefixedNamespace ns : services.getNamespaces()) { writer.writeNamespace(ns.getPrefix(), ns.getUri()); } } } private static void writeProperties(Iterable<EdmProperty> properties, XMLWriter2 writer) { for (EdmProperty prop : properties) { writer.startElement(new QName2("Property")); writer.writeAttribute("Name", prop.getName()); writer.writeAttribute("Type", prop.getType().getFullyQualifiedTypeName()); writer.writeAttribute("Nullable", Boolean.toString(prop.isNullable())); if (prop.getDefaultValue() != null) { writer.writeAttribute("DefaultValue", prop.getDefaultValue()); } if (prop.getMaxLength() != null) { writer.writeAttribute("MaxLength", Integer.toString(prop.getMaxLength())); } if (prop.getFixedLength() != null) { writer.writeAttribute("FixedLength", Boolean.toString(prop.getFixedLength())); } if (!prop.getCollectionKind().equals(CollectionKind.NONE)) { writer.writeAttribute("CollectionKind", prop.getCollectionKind().toString()); } if (prop.getPrecision() != null) { writer.writeAttribute("Precision", Integer.toString(prop.getPrecision())); } if (prop.getScale() != null) { writer.writeAttribute("Scale", Integer.toString(prop.getScale())); } if (prop.getCollation() != null) { writer.writeAttribute("Collation", prop.getCollation()); } if (prop.getUnicode() != null) { writer.writeAttribute("Unicode", Boolean.toString(prop.getUnicode())); } if (prop.getConcurrencyMode() != null) { writer.writeAttribute("ConcurrencyMode", prop.getConcurrencyMode()); } if (prop.getMimeType() != null) { writer.writeAttribute(new QName2(m, "MimeType", "m"), prop.getMimeType()); } if (prop.getFcTargetPath() != null) { writer.writeAttribute(new QName2(m, "FC_TargetPath", "m"), prop.getFcTargetPath()); } if (prop.getFcKeepInContent() != null) { writer.writeAttribute(new QName2(m, "FC_KeepInContent", "m"), prop.getFcKeepInContent()); } if (prop.getFcNsPrefix() != null) { writer.writeAttribute(new QName2(m, "FC_NsPrefix", "m"), prop.getFcNsPrefix()); } if (prop.getFcNsUri() != null) { writer.writeAttribute(new QName2(m, "FC_NsUri", "m"), prop.getFcNsUri()); } writeAnnotationAttributes(prop, writer); writeAnnotationElements(prop, writer); writer.endElement("Property"); } } private static void writeAnnotationAttributes(EdmItem item, XMLWriter2 writer) { if (item.getAnnotations() != null) { writeAnnotation(item.getAnnotations(), writer, null); } } private static void writeAnnotationAttributes(EdmAnnotationElement<?> element, XMLWriter2 writer, String prefix) { if (element.getAnnotations() != null) { writeAnnotation(element.getAnnotations(), writer, prefix); } } private static void writeAnnotation(Iterable<? extends NamespacedAnnotation<?>> annotList, XMLWriter2 writer, String prefix) { String prefix1 = prefix; for (NamespacedAnnotation<?> a : annotList) { if (a instanceof EdmAnnotationAttribute) { String prefix2 = a.getNamespace().getPrefix(); writer.writeAttribute( new QName2(a.getNamespace().getUri(), a.getName(), a.getNamespace().getPrefix()), a.getValue() == null ? "" : a.getValue().toString()); // if (!prefix2.equals(prefix1)) { // writer.writeNamespace(a.getNamespace().getPrefix(), a.getNamespace().getUri()); // } // prefix1 = prefix2; } } } private static void writeAnnotationElements(EdmItem item, XMLWriter2 writer) { if (item.getAnnotationElements() != null) { writeElementInAnnotation(item.getAnnotationElements(), writer); } } private static void writeAnnotationElements(EdmAnnotationElement<?> element, XMLWriter2 writer) { if (element.getAnnotationElements() != null) { writeElementInAnnotation(element.getAnnotationElements(), writer); } } private static void writeElementInAnnotation(Iterable<? extends NamespacedAnnotation<?>> annotList, XMLWriter2 writer) { for (NamespacedAnnotation<?> a : annotList) { if (a instanceof EdmAnnotationElement) { EdmAnnotationElement<?> elem = (EdmAnnotationElement<?>) a; writer.startElement(new QName2(elem.getNamespace().getUri(), elem.getName(), elem.getNamespace().getPrefix())); if (elem.getNamespace().getPrefix() != null && elem.getNamespace().getUri() != null) { writer.writeNamespace(elem.getNamespace().getPrefix(), elem.getNamespace().getUri()); } writeAnnotationAttributes(elem, writer, elem.getNamespace().getPrefix()); writer.writeText(elem.getValue().toString().trim()); writeAnnotationElements(elem, writer); writer.endElement(a.getName()); // TODO: please don't throw an exception here. // this totally breaks ODataConsumer even thought it doesn't rely // on annotations. A no-op is a interim approach that allows work // to proceed by those using queryable metadata to access annotations. // throw new UnsupportedOperationException("Implement element annotations"); } } } private static void writeDocumentation(EdmItem item, XMLWriter2 writer) { EdmDocumentation doc = item.getDocumentation(); if (doc != null && (doc.getSummary() != null || doc.getLongDescription() != null)) { QName2 d = new QName2(edm, "Documentation"); writer.startElement(d); { if (doc.getSummary() != null) { QName2 s = new QName2(edm, "Summary"); writer.startElement(s); writer.writeText(doc.getSummary()); writer.endElement(s.getLocalPart()); } if (doc.getLongDescription() != null) { QName2 s = new QName2(edm, "LongDescription"); writer.startElement(s); writer.writeText(doc.getLongDescription()); writer.endElement(s.getLocalPart()); } } writer.endElement(d.getLocalPart()); } } }