Repository: olingo-odata4 Updated Branches: refs/heads/OLINGO-832_StreamSerializerPoC 5b6cccfa9 -> 396a39bae
OLINGO-878: Adding support to remove unvalid xml characters from Atom payload Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/d880d6c4 Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/d880d6c4 Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/d880d6c4 Branch: refs/heads/OLINGO-832_StreamSerializerPoC Commit: d880d6c48058d9413bb914a5bd3c13f70a9db323 Parents: 3c205f9 Author: Ramesh Reddy <[email protected]> Authored: Thu Feb 11 14:12:47 2016 -0600 Committer: Ramesh Reddy <[email protected]> Committed: Fri Feb 12 19:37:58 2016 -0600 ---------------------------------------------------------------------- .../serializer/ComplexSerializerOptions.java | 14 +- .../EntityCollectionSerializerOptions.java | 14 +- .../api/serializer/EntitySerializerOptions.java | 14 +- .../serializer/PrimitiveSerializerOptions.java | 13 ++ .../PrimitiveValueSerializerOptions.java | 14 +- .../olingo/server/core/MetadataParser.java | 199 +++++++++++++++---- .../server/core/SchemaBasedEdmProvider.java | 86 +++----- .../olingo/server/core/ServiceRequest.java | 50 ++++- .../server/core/requests/DataRequest.java | 13 +- .../src/main/resources/org.apache.olingo.v1.xml | 40 ++++ .../core/MetadataParserAnnotationsTest.java | 2 +- .../olingo/server/core/MetadataParserTest.java | 1 - .../server/core/ServiceDispatcherTest.java | 3 + .../server/example/TripPinServiceTest.java | 25 ++- .../olingo/server/example/TripPinServlet.java | 3 +- .../src/test/resources/airlines.json | 2 +- .../src/test/resources/annotations.xml | 3 + .../src/test/resources/trippin.xml | 6 +- .../core/serializer/xml/ODataXmlSerializer.java | 138 +++++++++---- .../serializer/xml/ODataXmlSerializerTest.java | 27 ++- 20 files changed, 498 insertions(+), 169 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d880d6c4/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/ComplexSerializerOptions.java ---------------------------------------------------------------------- diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/ComplexSerializerOptions.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/ComplexSerializerOptions.java index d6788e5..179c4d4 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/ComplexSerializerOptions.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/ComplexSerializerOptions.java @@ -28,6 +28,7 @@ public class ComplexSerializerOptions { private ContextURL contextURL; private ExpandOption expand; private SelectOption select; + private String xml10InvalidCharReplacement; /** Gets the {@link ContextURL}. */ public ContextURL getContextURL() { @@ -44,6 +45,11 @@ public class ComplexSerializerOptions { return select; } + /** Gets the replacement string for unicode characters, that is not allowed in XML 1.0 */ + public String xml10InvalidCharReplacement() { + return xml10InvalidCharReplacement; + } + private ComplexSerializerOptions() {} /** Initializes the options builder. */ @@ -77,7 +83,13 @@ public class ComplexSerializerOptions { options.select = select; return this; } - + + /** set the replacement string for xml 1.0 unicode controlled characters that are not allowed */ + public Builder xml10InvalidCharReplacement(final String replacement) { + options.xml10InvalidCharReplacement = replacement; + return this; + } + /** Builds the OData serializer options. */ public ComplexSerializerOptions build() { return options; http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d880d6c4/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EntityCollectionSerializerOptions.java ---------------------------------------------------------------------- diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EntityCollectionSerializerOptions.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EntityCollectionSerializerOptions.java index 611485f..9ee7d24 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EntityCollectionSerializerOptions.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EntityCollectionSerializerOptions.java @@ -32,6 +32,7 @@ public class EntityCollectionSerializerOptions { private SelectOption select; private boolean writeOnlyReferences; private String id; + private String xml10InvalidCharReplacement; /** Gets the {@link ContextURL}. */ public ContextURL getContextURL() { @@ -63,6 +64,11 @@ public class EntityCollectionSerializerOptions { return id; } + /** Gets the replacement string for unicode characters, that is not allowed in XML 1.0 */ + public String xml10InvalidCharReplacement() { + return xml10InvalidCharReplacement; + } + /** Initializes the options builder. */ public static Builder with() { return new Builder(); @@ -112,7 +118,13 @@ public class EntityCollectionSerializerOptions { options.id = id; return this; } - + + /** set the replacement String for xml 1.0 unicode controlled characters that are not allowed */ + public Builder xml10InvalidCharReplacement(final String replacement) { + options.xml10InvalidCharReplacement = replacement; + return this; + } + /** Builds the OData serializer options. */ public EntityCollectionSerializerOptions build() { return options; http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d880d6c4/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EntitySerializerOptions.java ---------------------------------------------------------------------- diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EntitySerializerOptions.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EntitySerializerOptions.java index 16481a2..e244660 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EntitySerializerOptions.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EntitySerializerOptions.java @@ -28,6 +28,7 @@ public class EntitySerializerOptions { private ExpandOption expand; private SelectOption select; private boolean writeOnlyReferences; + private String xml10InvalidCharReplacement; /** Gets the {@link ContextURL}. */ public ContextURL getContextURL() { @@ -49,6 +50,11 @@ public class EntitySerializerOptions { return writeOnlyReferences; } + /** Gets the replacement string for unicode characters, that is not allowed in XML 1.0 */ + public String xml10InvalidCharReplacement() { + return xml10InvalidCharReplacement; + } + private EntitySerializerOptions() {} /** Initializes the options builder. */ @@ -88,7 +94,13 @@ public class EntitySerializerOptions { options.writeOnlyReferences = ref; return this; } - + + /** set the replacement string for xml 1.0 unicode controlled characters that are not allowed */ + public Builder xml10InvalidCharReplacement(final String replacement) { + options.xml10InvalidCharReplacement = replacement; + return this; + } + /** Builds the OData serializer options. */ public EntitySerializerOptions build() { return options; http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d880d6c4/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/PrimitiveSerializerOptions.java ---------------------------------------------------------------------- diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/PrimitiveSerializerOptions.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/PrimitiveSerializerOptions.java index 1de20d8..61a3160 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/PrimitiveSerializerOptions.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/PrimitiveSerializerOptions.java @@ -30,6 +30,7 @@ public final class PrimitiveSerializerOptions { private Integer precision; private Integer scale; private Boolean isUnicode; + private String xml10InvalidCharReplacement; /** Gets the {@link ContextURL}. */ public ContextURL getContextURL() { @@ -60,6 +61,12 @@ public final class PrimitiveSerializerOptions { public Boolean isUnicode() { return isUnicode; } + + /** Gets the replacement string for unicode characters, that is not allowed in XML 1.0 */ + public String xml10InvalidCharReplacement() { + return xml10InvalidCharReplacement; + } + private PrimitiveSerializerOptions() {} @@ -123,6 +130,12 @@ public final class PrimitiveSerializerOptions { return this; } + /** set the replacement string for xml 1.0 unicode controlled characters that are not allowed */ + public Builder xml10InvalidCharReplacement(final String replacement) { + options.xml10InvalidCharReplacement = replacement; + return this; + } + /** Builds the OData serializer options. */ public PrimitiveSerializerOptions build() { return options; http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d880d6c4/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/PrimitiveValueSerializerOptions.java ---------------------------------------------------------------------- diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/PrimitiveValueSerializerOptions.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/PrimitiveValueSerializerOptions.java index e84aaa4..c72f420 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/PrimitiveValueSerializerOptions.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/PrimitiveValueSerializerOptions.java @@ -28,6 +28,7 @@ public class PrimitiveValueSerializerOptions { private Integer precision; private Integer scale; private Boolean isUnicode; + private String xml10InvalidCharReplacement; /** Gets the nullable facet. */ public Boolean isNullable() { @@ -53,6 +54,11 @@ public class PrimitiveValueSerializerOptions { public Boolean isUnicode() { return isUnicode; } + + /** Gets the replacement string for unicode characters, that is not allowed in XML 1.0 */ + public String xml10InvalidCharReplacement() { + return xml10InvalidCharReplacement; + } private PrimitiveValueSerializerOptions() {} @@ -109,7 +115,13 @@ public class PrimitiveValueSerializerOptions { options.isUnicode = property.isUnicode(); return this; } - + + /** set the replacement string for xml 1.0 unicode controlled characters that are not allowed */ + public Builder xml10InvalidCharReplacement(final String replacement) { + options.xml10InvalidCharReplacement = replacement; + return this; + } + /** Builds the OData serializer options. */ public PrimitiveValueSerializerOptions build() { return options; http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d880d6c4/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/MetadataParser.java ---------------------------------------------------------------------- diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/MetadataParser.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/MetadataParser.java index 03a6675..b93ef4c 100644 --- a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/MetadataParser.java +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/MetadataParser.java @@ -97,57 +97,93 @@ import org.apache.olingo.server.api.edmx.EdmxReferenceIncludeAnnotation; public class MetadataParser { private boolean parseAnnotations = false; private static final String XML_LINK_NS = "http://www.w3.org/1999/xlink"; - private ReferenceResolver defaultReferenceResolver = new DefaultReferenceResolver(); - private boolean loadCoreVocabularies = false; + private ReferenceResolver referenceResolver = new DefaultReferenceResolver(); + private boolean useLocalCoreVocabularies = true; + private boolean implicitlyLoadCoreVocabularies = false; + /** + * Avoid reading the annotations in the $metadata + * @param parse + * @return + */ public MetadataParser parseAnnotations(boolean parse) { this.parseAnnotations = parse; return this; } - + + /** + * Externalize the reference loading, such that they can be loaded from local caches + * @param resolver + * @return + */ public MetadataParser referenceResolver(ReferenceResolver resolver) { - this.defaultReferenceResolver = resolver; + this.referenceResolver = resolver; return this; } - public MetadataParser loadCoreVocabularies(boolean load) { - this.loadCoreVocabularies = load; + /** + * Load the core libraries from local classpath + * @param load true for yes; false otherwise + * @return + */ + public MetadataParser useLocalCoreVocabularies(boolean load) { + this.useLocalCoreVocabularies = load; + return this; + } + + /** + * Load the core vocabularies, irrespective of if they are defined in the $metadata + * @param load + * @return + */ + public MetadataParser implicitlyLoadCoreVocabularies(boolean load) { + this.implicitlyLoadCoreVocabularies = load; return this; } public ServiceMetadata buildServiceMetadata(Reader csdl) throws XMLStreamException { - SchemaBasedEdmProvider provider = buildEdmProvider(csdl, - this.defaultReferenceResolver, this.loadCoreVocabularies); + SchemaBasedEdmProvider provider = buildEdmProvider(csdl, this.referenceResolver, + this.implicitlyLoadCoreVocabularies, this.useLocalCoreVocabularies); return new ServiceMetadataImpl(provider, provider.getReferences(), null); } public SchemaBasedEdmProvider buildEdmProvider(Reader csdl) throws XMLStreamException { - return buildEdmProvider(csdl, this.defaultReferenceResolver, this.loadCoreVocabularies); + XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + XMLEventReader reader = xmlInputFactory.createXMLEventReader(csdl); + return buildEdmProvider(reader, this.referenceResolver, + this.implicitlyLoadCoreVocabularies, this.useLocalCoreVocabularies); } - + protected SchemaBasedEdmProvider buildEdmProvider(Reader csdl, - ReferenceResolver referenceResolver, boolean loadCoreVocabularies) throws XMLStreamException { + ReferenceResolver resolver, boolean loadCore, boolean useLocal) + throws XMLStreamException { XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); - XMLEventReader reader = xmlInputFactory.createXMLEventReader(csdl); - return buildEdmProvider(reader, referenceResolver, loadCoreVocabularies); + XMLEventReader reader = xmlInputFactory.createXMLEventReader(csdl); + return buildEdmProvider(reader, resolver, loadCore, useLocal); } - + protected SchemaBasedEdmProvider buildEdmProvider(InputStream csdl, - ReferenceResolver referenceResolver, boolean loadCoreVocabularies) throws XMLStreamException { + ReferenceResolver resolver, boolean loadCore, boolean useLocal) + throws XMLStreamException { XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); XMLEventReader reader = xmlInputFactory.createXMLEventReader(csdl); - return buildEdmProvider(reader, referenceResolver, loadCoreVocabularies); + return buildEdmProvider(reader, resolver, loadCore, useLocal); } protected SchemaBasedEdmProvider buildEdmProvider(XMLEventReader reader, - ReferenceResolver referenceResolver, boolean loadCoreVocabularies) throws XMLStreamException { - SchemaBasedEdmProvider provider = new SchemaBasedEdmProvider(referenceResolver); + ReferenceResolver resolver, boolean loadCore, boolean useLocal) + throws XMLStreamException { + SchemaBasedEdmProvider provider = new SchemaBasedEdmProvider(); + + final StringBuilder xmlBase = new StringBuilder(); + new ElementReader<SchemaBasedEdmProvider>() { @Override void build(XMLEventReader reader, StartElement element, SchemaBasedEdmProvider provider, String name) throws XMLStreamException { - String xmlBase = attrNS(element, XML_LINK_NS, "base"); - provider.setXMLBase(xmlBase); + if (attrNS(element, XML_LINK_NS, "base") != null) { + xmlBase.append(attrNS(element, XML_LINK_NS, "base")); + } String version = attr(element, "Version"); if ("4.0".equals(version)) { readDataServicesAndReference(reader, element, provider); @@ -167,23 +203,100 @@ public class MetadataParser { event.asEndElement().getName().getLocalPart())); } - // load the core vocabularies - if (loadCoreVocabularies) { - loadVocabularySchema(provider, "Org.OData.Core.V1", "Org.OData.Core.V1.xml"); - loadVocabularySchema(provider, "Org.OData.Capabilities.V1", "Org.OData.Capabilities.V1.xml"); - loadVocabularySchema(provider, "Org.OData.Measures.V1", "Org.OData.Measures.V1.xml"); - } + //load core vocabularies even though they are not defined in the references + if (loadCore) { + loadCoreVocabulary(provider, "Org.OData.Core.V1"); + loadCoreVocabulary(provider, "Org.OData.Capabilities.V1"); + loadCoreVocabulary(provider, "Org.OData.Measures.V1"); + } + + // load all the reference schemas + if (resolver != null) { + loadReferencesSchemas(provider, xmlBase.length() == 0 ? null + : fixXmlBase(xmlBase.toString()), resolver, loadCore, useLocal); + } return provider; } - private void loadVocabularySchema(SchemaBasedEdmProvider provider, String namespace, + private void loadReferencesSchemas(SchemaBasedEdmProvider provider, + String xmlBase, ReferenceResolver resolver, boolean loadCore, + boolean useLocal) { + + for (EdmxReference reference:provider.getReferences()) { + try { + SchemaBasedEdmProvider refProvider = null; + + for (EdmxReferenceInclude include : reference.getIncludes()) { + + // check if the schema is already loaded before. + if (provider.getSchema(include.getNamespace()) != null) { + continue; + } + + if (isCoreVocabulary(include.getNamespace()) && useLocal) { + loadCoreVocabulary(provider, include.getNamespace()); + continue; + } + + if (refProvider == null) { + InputStream is = this.referenceResolver.resolveReference(reference.getUri(), xmlBase); + if (is == null) { + throw new EdmException("Failed to load Reference "+reference.getUri()+" loading failed"); + } else { + // do not implicitly load core vocabularies any more. But if the + // references loading the core vocabularies try to use local if we can + refProvider = buildEdmProvider(is, resolver, false, useLocal); + } + } + + CsdlSchema refSchema = refProvider.getSchema(include.getNamespace(), false); + provider.addReferenceSchema(include.getNamespace(), refProvider); + if (include.getAlias() != null) { + refSchema.setAlias(include.getAlias()); + provider.addReferenceSchema(include.getAlias(), refProvider); + } + } + } catch (XMLStreamException e) { + throw new EdmException("Failed to load Reference "+reference.getUri()+" parsing failed"); + } + } + } + + private void loadCoreVocabulary(SchemaBasedEdmProvider provider, + String namespace) throws XMLStreamException { + if(namespace.equalsIgnoreCase("Org.OData.Core.V1")) { + loadLocalVocabularySchema(provider, "Org.OData.Core.V1", "Org.OData.Core.V1.xml"); + } else if (namespace.equalsIgnoreCase("Org.OData.Capabilities.V1")) { + loadLocalVocabularySchema(provider, "Org.OData.Capabilities.V1", "Org.OData.Capabilities.V1.xml"); + } else if (namespace.equalsIgnoreCase("Org.OData.Measures.V1")) { + loadLocalVocabularySchema(provider, "Org.OData.Measures.V1", "Org.OData.Measures.V1.xml"); + } + } + + private boolean isCoreVocabulary(String namespace) { + if(namespace.equalsIgnoreCase("Org.OData.Core.V1") || + namespace.equalsIgnoreCase("Org.OData.Capabilities.V1") || + namespace.equalsIgnoreCase("Org.OData.Measures.V1")) { + return true; + } + return false; + } + + private String fixXmlBase(String base) { + if (base.endsWith("/")) { + return base; + } + return base+"/"; + } + + private void loadLocalVocabularySchema(SchemaBasedEdmProvider provider, String namespace, String resource) throws XMLStreamException { - CsdlSchema schema = provider.getSchema(namespace, false); + CsdlSchema schema = provider.getVocabularySchema(namespace); if (schema == null) { InputStream is = this.getClass().getClassLoader().getResourceAsStream(resource); if (is != null) { - SchemaBasedEdmProvider childProvider = buildEdmProvider(is, null, false); - provider.addSchema(childProvider.getSchema(namespace, false)); + SchemaBasedEdmProvider childProvider = buildEdmProvider(is, null, false, false); + provider.addVocabularySchema(namespace, childProvider); } else { throw new XMLStreamException("failed to load "+resource+" core vocabulary"); } @@ -191,7 +304,8 @@ public class MetadataParser { } private void readDataServicesAndReference(XMLEventReader reader, - StartElement element, SchemaBasedEdmProvider provider) throws XMLStreamException { + StartElement element, SchemaBasedEdmProvider provider) + throws XMLStreamException { final ArrayList<EdmxReference> references = new ArrayList<EdmxReference>(); new ElementReader<SchemaBasedEdmProvider>() { @Override @@ -384,7 +498,9 @@ public class MetadataParser { CsdlTypeDefinition td = new CsdlTypeDefinition(); td.setName(attr(element, "Name")); td.setUnderlyingType(new FullQualifiedName(attr(element, "UnderlyingType"))); - td.setUnicode(Boolean.parseBoolean(attr(element, "Unicode"))); + if (attr(element, "Unicode") != null) { + td.setUnicode(Boolean.parseBoolean(attr(element, "Unicode"))); + } String maxLength = attr(element, "MaxLength"); if (maxLength != null) { @@ -837,7 +953,9 @@ public class MetadataParser { property.setCollection(isCollectionType(element)); property.setNullable(Boolean.parseBoolean(attr(element, "Nullable") == null ? "true" : attr( element, "Nullable"))); - property.setUnicode(Boolean.parseBoolean(attr(element, "Unicode"))); + if (attr(element, "Unicode") != null) { + property.setUnicode(Boolean.parseBoolean(attr(element, "Unicode"))); + } String maxLength = attr(element, "MaxLength"); if (maxLength != null) { @@ -1077,18 +1195,23 @@ public class MetadataParser { private static class DefaultReferenceResolver implements ReferenceResolver { @Override public InputStream resolveReference(URI referenceUri, String xmlBase) { - URL schemaURL = null; + InputStream in = null; try { if (referenceUri.isAbsolute()) { - schemaURL = referenceUri.toURL(); + URL schemaURL = referenceUri.toURL(); + in = schemaURL.openStream(); } else { if (xmlBase != null) { - schemaURL = new URL(xmlBase+referenceUri.toString()); + URL schemaURL = new URL(xmlBase+referenceUri.toString()); + in = schemaURL.openStream(); } else { - throw new EdmException("No xml:base set to read the references from the metadata"); + in = this.getClass().getClassLoader().getResourceAsStream(referenceUri.getPath()); + if (in == null) { + throw new EdmException("No xml:base set to read the references from the metadata"); + } } } - return schemaURL.openStream(); + return in; } catch (MalformedURLException e) { throw new EdmException(e); } catch (IOException e) { http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d880d6c4/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/SchemaBasedEdmProvider.java ---------------------------------------------------------------------- diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/SchemaBasedEdmProvider.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/SchemaBasedEdmProvider.java index a778e2c..8aea555 100644 --- a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/SchemaBasedEdmProvider.java +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/SchemaBasedEdmProvider.java @@ -18,15 +18,11 @@ */ package org.apache.olingo.server.core; -import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import javax.xml.stream.XMLStreamException; - -import org.apache.olingo.commons.api.edm.EdmException; import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.commons.api.edm.provider.CsdlAction; import org.apache.olingo.commons.api.edm.provider.CsdlActionImport; @@ -52,15 +48,11 @@ import org.apache.olingo.server.api.edmx.EdmxReferenceInclude; public class SchemaBasedEdmProvider implements CsdlEdmProvider { private final List<CsdlSchema> edmSchemas = new ArrayList<CsdlSchema>(); private final Map<String, EdmxReference> references = new ConcurrentHashMap<String, EdmxReference>(); - private final Map<String, SchemaBasedEdmProvider> referenceSchemas - = new ConcurrentHashMap<String, SchemaBasedEdmProvider>(); - private String xmlBase; - private ReferenceResolver referenceResolver; + private final Map<String, SchemaBasedEdmProvider> referenceSchemas = + new ConcurrentHashMap<String, SchemaBasedEdmProvider>(); + private final Map<String, SchemaBasedEdmProvider> coreVocabularySchemas = + new ConcurrentHashMap<String, SchemaBasedEdmProvider>(); - public SchemaBasedEdmProvider(ReferenceResolver referenceResolver) { - this.referenceResolver = referenceResolver; - } - void addSchema(CsdlSchema schema) { this.edmSchemas.add(schema); } @@ -68,7 +60,23 @@ public class SchemaBasedEdmProvider implements CsdlEdmProvider { List<EdmxReference> getReferences(){ return new ArrayList<EdmxReference>(references.values()); } - + + void addReferenceSchema(String ns, SchemaBasedEdmProvider provider) { + this.referenceSchemas.put(ns, provider); + } + + void addVocabularySchema(String ns, SchemaBasedEdmProvider provider) { + this.coreVocabularySchemas.put(ns, provider); + } + + CsdlSchema getVocabularySchema(String ns) { + SchemaBasedEdmProvider provider = this.coreVocabularySchemas.get(ns); + if (provider != null) { + return provider.getSchema(ns, false); + } + return null; + } + CsdlSchema getSchema(String ns) { return getSchema(ns, true); } @@ -79,46 +87,20 @@ public class SchemaBasedEdmProvider implements CsdlEdmProvider { return s; } } + CsdlSchema s = null; if (checkReferences) { - return getReferenceSchema(ns); + s = getReferenceSchema(ns); + if (s == null) { + s = getVocabularySchema(ns); + } } - return null; + return s; } - private CsdlSchema getReferenceSchema(String ns) { + CsdlSchema getReferenceSchema(String ns) { if (ns == null) { return null; } - if (this.referenceSchemas.get(ns) == null) { - EdmxReference reference = this.references.get(ns); - if (reference != null) { - SchemaBasedEdmProvider provider = null; - if (this.referenceResolver == null) { - throw new EdmException("Failed to load Reference "+reference.getUri()); - } else { - InputStream is = this.referenceResolver.resolveReference(reference.getUri(), this.xmlBase); - if (is != null) { - try { - MetadataParser parser = new MetadataParser(); - provider = parser.buildEdmProvider(is, this.referenceResolver, false); - } catch (XMLStreamException e) { - throw new EdmException("Failed to load Reference "+reference.getUri()+" parsing failed"); - } - } else { - throw new EdmException("Failed to load Reference "+reference.getUri()+" loading failed"); - } - } - // copy references - for (EdmxReferenceInclude include : reference.getIncludes()) { - this.referenceSchemas.put(include.getNamespace(), provider); - if (include.getAlias() != null) { - CsdlSchema schema = provider.getSchema(include.getNamespace()); - schema.setAlias(include.getAlias()); - this.referenceSchemas.put(include.getAlias(), provider); - } - } - } - } if (this.referenceSchemas.get(ns) != null) { return this.referenceSchemas.get(ns).getSchema(ns); @@ -133,7 +115,7 @@ public class SchemaBasedEdmProvider implements CsdlEdmProvider { } return null; } - + @Override public CsdlEnumType getEnumType(FullQualifiedName fqn) throws ODataException { CsdlSchema schema = getSchema(fqn.getNamespace()); @@ -402,15 +384,5 @@ public class SchemaBasedEdmProvider implements CsdlEdmProvider { } } } - } - - public void setXMLBase(String base) { - if (base != null) { - if (base.endsWith("/")) { - this.xmlBase = base; - } else { - this.xmlBase = base+"/"; - } - } } } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d880d6c4/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceRequest.java ---------------------------------------------------------------------- diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceRequest.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceRequest.java index 0796144..f6b3296 100644 --- a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceRequest.java +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceRequest.java @@ -27,14 +27,18 @@ import java.util.Map; import java.util.StringTokenizer; import org.apache.olingo.commons.api.data.ContextURL; +import org.apache.olingo.commons.api.edm.EdmAnnotation; +import org.apache.olingo.commons.api.edm.EdmSchema; +import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.commons.api.edm.annotation.EdmConstantExpression; import org.apache.olingo.commons.api.format.ContentType; import org.apache.olingo.commons.api.http.HttpHeader; import org.apache.olingo.commons.api.http.HttpMethod; import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataLibraryException; import org.apache.olingo.server.api.ODataRequest; import org.apache.olingo.server.api.ODataResponse; -import org.apache.olingo.server.api.ODataLibraryException; import org.apache.olingo.server.api.ServiceMetadata; import org.apache.olingo.server.api.serializer.ComplexSerializerOptions; import org.apache.olingo.server.api.serializer.CustomContentTypeSupport; @@ -140,26 +144,60 @@ public abstract class ServiceRequest { return this.request.getMethod() == HttpMethod.POST; } + private static FullQualifiedName XML10_CHAR_REPLACE_FQN = new FullQualifiedName( + "org.apache.olingo.v1.xml10-incompatible-char-replacement"); + /** + * Replacement character for the XML10 characters that are not supported. + * @return + */ + protected String xml10IncompatibleCharReplacement() { + for (EdmSchema schema : getServiceMetaData().getEdm().getSchemas()) { + if (schema.getEntityContainer() != null) { + for (EdmAnnotation annotation:schema.getAnnotations()) { + if (annotation.getTerm() != null + && annotation.getTerm().getFullQualifiedName().equals(XML10_CHAR_REPLACE_FQN)) { + EdmConstantExpression expr = annotation.getExpression().asConstant(); + return expr.getValueAsString(); + } + } + } + } + return null; + } + @SuppressWarnings("unchecked") - public <T> T getSerializerOptions(Class<T> serilizerOptions, ContextURL contextUrl, - boolean references) throws ContentNegotiatorException { + public <T> T getSerializerOptions(Class<T> serilizerOptions, + ContextURL contextUrl, boolean references) throws ContentNegotiatorException { + + String xmlReplacement = null; + if (getResponseContentType().isCompatible(ContentType.APPLICATION_XML) + || getResponseContentType().isCompatible(ContentType.APPLICATION_ATOM_XML)) { + xmlReplacement = xml10IncompatibleCharReplacement(); + } if (serilizerOptions.isAssignableFrom(EntitySerializerOptions.class)) { return (T) EntitySerializerOptions.with() .contextURL(isODataMetadataNone(getResponseContentType()) ? null : contextUrl) .expand(uriInfo.getExpandOption()).select(this.uriInfo.getSelectOption()) - .writeOnlyReferences(references).build(); + .writeOnlyReferences(references) + .xml10InvalidCharReplacement(xmlReplacement) + .build(); } else if (serilizerOptions.isAssignableFrom(EntityCollectionSerializerOptions.class)) { return (T) EntityCollectionSerializerOptions.with() .contextURL(isODataMetadataNone(getResponseContentType()) ? null : contextUrl) .count(uriInfo.getCountOption()).expand(uriInfo.getExpandOption()) .select(uriInfo.getSelectOption()).writeOnlyReferences(references) - .id(getODataRequest().getRawBaseUri() + getODataRequest().getRawODataPath()).build(); + .id(getODataRequest().getRawBaseUri() + getODataRequest().getRawODataPath()) + .xml10InvalidCharReplacement(xmlReplacement) + .build(); } else if (serilizerOptions.isAssignableFrom(ComplexSerializerOptions.class)) { return (T) ComplexSerializerOptions.with().contextURL(contextUrl) - .expand(this.uriInfo.getExpandOption()).select(this.uriInfo.getSelectOption()).build(); + .expand(this.uriInfo.getExpandOption()).select(this.uriInfo.getSelectOption()) + .xml10InvalidCharReplacement(xmlReplacement) + .build(); } else if (serilizerOptions.isAssignableFrom(PrimitiveSerializerOptions.class)) { return (T) PrimitiveSerializerOptions.with().contextURL(contextUrl) + .xml10InvalidCharReplacement(xmlReplacement) .build(); } return null; http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d880d6c4/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/DataRequest.java ---------------------------------------------------------------------- diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/DataRequest.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/DataRequest.java index 42fbdea..45e1ed6 100644 --- a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/DataRequest.java +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/DataRequest.java @@ -248,10 +248,19 @@ public class DataRequest extends ServiceRequest { @SuppressWarnings("unchecked") @Override public <T> T getSerializerOptions(Class<T> serilizerOptions, ContextURL contextUrl, boolean references) - throws ContentNegotiatorException { + throws ContentNegotiatorException { if (serilizerOptions.isAssignableFrom(PrimitiveSerializerOptions.class)) { + + String xmlReplacement = null; + if (getResponseContentType().isCompatible(ContentType.APPLICATION_XML) + || getResponseContentType().isCompatible(ContentType.APPLICATION_ATOM_XML)) { + xmlReplacement = xml10IncompatibleCharReplacement(); + } + return (T) PrimitiveSerializerOptions.with().contextURL(contextUrl) - .facetsFrom(getUriResourceProperty().getProperty()).build(); + .facetsFrom(getUriResourceProperty().getProperty()) + .xml10InvalidCharReplacement(xmlReplacement) + .build(); } return super.getSerializerOptions(serilizerOptions, contextUrl, references); } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d880d6c4/lib/server-core-ext/src/main/resources/org.apache.olingo.v1.xml ---------------------------------------------------------------------- diff --git a/lib/server-core-ext/src/main/resources/org.apache.olingo.v1.xml b/lib/server-core-ext/src/main/resources/org.apache.olingo.v1.xml new file mode 100644 index 0000000..d197e45 --- /dev/null +++ b/lib/server-core-ext/src/main/resources/org.apache.olingo.v1.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + + 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. + +--> + +<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0"> + <edmx:Reference Uri="http://docs.oasis-open.org/odata/odata/v4.0/os/vocabularies/Org.OData.Core.V1.xml"> + <edmx:Include Alias="Core" Namespace="Org.OData.Core.V1" /> + </edmx:Reference> + <edmx:DataServices> + <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="org.apache.olingo.v1" Alias="olingo-extensions"> + + <Term Name="xml10-incompatible-char-replacement" Type="Edm.String" AppliesTo="PropertyValue ReturnType"> + <Annotation Term="Core.Description"> + <String> + Replacement character for invalid characters in the XML 1.0 Atom payload + </String> + </Annotation> + </Term> + + </Schema> + </edmx:DataServices> +</edmx:Edmx> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d880d6c4/lib/server-core-ext/src/test/java/org/apache/olingo/server/core/MetadataParserAnnotationsTest.java ---------------------------------------------------------------------- diff --git a/lib/server-core-ext/src/test/java/org/apache/olingo/server/core/MetadataParserAnnotationsTest.java b/lib/server-core-ext/src/test/java/org/apache/olingo/server/core/MetadataParserAnnotationsTest.java index d71c54d..75c12ad 100644 --- a/lib/server-core-ext/src/test/java/org/apache/olingo/server/core/MetadataParserAnnotationsTest.java +++ b/lib/server-core-ext/src/test/java/org/apache/olingo/server/core/MetadataParserAnnotationsTest.java @@ -60,7 +60,7 @@ public class MetadataParserAnnotationsTest { public void setUp() throws Exception { MetadataParser parser = new MetadataParser(); parser.parseAnnotations(true); - parser.loadCoreVocabularies(true); + parser.useLocalCoreVocabularies(true); provider = (CsdlEdmProvider) parser.buildEdmProvider(new FileReader("src/test/resources/annotations.xml")); } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d880d6c4/lib/server-core-ext/src/test/java/org/apache/olingo/server/core/MetadataParserTest.java ---------------------------------------------------------------------- diff --git a/lib/server-core-ext/src/test/java/org/apache/olingo/server/core/MetadataParserTest.java b/lib/server-core-ext/src/test/java/org/apache/olingo/server/core/MetadataParserTest.java index 83160b4..8afe5d2 100644 --- a/lib/server-core-ext/src/test/java/org/apache/olingo/server/core/MetadataParserTest.java +++ b/lib/server-core-ext/src/test/java/org/apache/olingo/server/core/MetadataParserTest.java @@ -54,7 +54,6 @@ public class MetadataParserTest { @Before public void setUp() throws Exception { MetadataParser parser = new MetadataParser(); - parser.parseAnnotations(true); provider = (CsdlEdmProvider) parser.buildEdmProvider(new FileReader("src/test/resources/trippin.xml")); } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d880d6c4/lib/server-core-ext/src/test/java/org/apache/olingo/server/core/ServiceDispatcherTest.java ---------------------------------------------------------------------- diff --git a/lib/server-core-ext/src/test/java/org/apache/olingo/server/core/ServiceDispatcherTest.java b/lib/server-core-ext/src/test/java/org/apache/olingo/server/core/ServiceDispatcherTest.java index 90ead94..ab4a62a 100644 --- a/lib/server-core-ext/src/test/java/org/apache/olingo/server/core/ServiceDispatcherTest.java +++ b/lib/server-core-ext/src/test/java/org/apache/olingo/server/core/ServiceDispatcherTest.java @@ -91,6 +91,9 @@ public class ServiceDispatcherTest { public void beforeTest(ServiceHandler serviceHandler) throws Exception { MetadataParser parser = new MetadataParser(); + parser.parseAnnotations(true); + parser.useLocalCoreVocabularies(true); + parser.implicitlyLoadCoreVocabularies(true); ServiceMetadata metadata = parser.buildServiceMetadata(new FileReader("src/test/resources/trippin.xml")); File baseDir = new File(System.getProperty("java.io.tmpdir")); http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d880d6c4/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinServiceTest.java ---------------------------------------------------------------------- diff --git a/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinServiceTest.java b/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinServiceTest.java index 3beb274..ec0c7c7 100644 --- a/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinServiceTest.java +++ b/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinServiceTest.java @@ -114,20 +114,23 @@ public class TripPinServiceTest { } @Test - public void testEntitySet() throws Exception { - HttpRequest req = new HttpGet(baseURL+"/People"); - req.setHeader("Content-Type", "application/json;odata.metadata=minimal"); + public void testXMLInvalidChars() throws Exception { + HttpRequest req = new HttpGet(baseURL+"/Airlines('FM')"); + req.setHeader("Accept", "application/xml"); HttpResponse response = httpSend(req, 200); - JsonNode node = getJSONNode(response); - - assertEquals("$metadata#People", node.get("@odata.context").asText()); - assertEquals(baseURL+"/People?$skiptoken=8", node.get("@odata.nextLink").asText()); - - JsonNode person = ((ArrayNode)node.get("value")).get(0); - assertEquals("russellwhyte", person.get("UserName").asText()); + String actual = IOUtils.toString(response.getEntity().getContent()); + String expected = + "<m:properties>" + + "<d:AirlineCode>FM</d:AirlineCode>" + + "<d:Name>Shanghai xxxAirlinexxx</d:Name>" + + "<d:Picture m:null=\"true\"/>" + + "</m:properties>" + + "</a:content>" + +"</a:entry>"; + assertTrue(actual.endsWith(expected)); } - + @Test public void testReadEntitySetWithPaging() throws Exception { String url = baseURL+"/People"; http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d880d6c4/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinServlet.java ---------------------------------------------------------------------- diff --git a/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinServlet.java b/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinServlet.java index 06e498c..7ab019d 100644 --- a/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinServlet.java +++ b/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinServlet.java @@ -51,7 +51,8 @@ public class TripPinServlet extends HttpServlet { try { parser.parseAnnotations(true); - parser.loadCoreVocabularies(true); + parser.useLocalCoreVocabularies(true); + parser.implicitlyLoadCoreVocabularies(true); metadata = parser.buildServiceMetadata(new FileReader("src/test/resources/trippin.xml")); } catch (XMLStreamException e) { throw new IOException(e); http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d880d6c4/lib/server-core-ext/src/test/resources/airlines.json ---------------------------------------------------------------------- diff --git a/lib/server-core-ext/src/test/resources/airlines.json b/lib/server-core-ext/src/test/resources/airlines.json index 1d93aa7..9019c97 100644 --- a/lib/server-core-ext/src/test/resources/airlines.json +++ b/lib/server-core-ext/src/test/resources/airlines.json @@ -10,7 +10,7 @@ }, { "AirlineCode":"FM", - "Name":"Shanghai Airline" + "Name":"Shanghai \u0000Airline\u0001" }, { "AirlineCode":"MU", http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d880d6c4/lib/server-core-ext/src/test/resources/annotations.xml ---------------------------------------------------------------------- diff --git a/lib/server-core-ext/src/test/resources/annotations.xml b/lib/server-core-ext/src/test/resources/annotations.xml index 1c5281a..3c8570f 100644 --- a/lib/server-core-ext/src/test/resources/annotations.xml +++ b/lib/server-core-ext/src/test/resources/annotations.xml @@ -11,6 +11,9 @@ language governing permissions and limitations under the License. --> <edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0"> + <edmx:Reference Uri="http://docs.oasis-open.org/odata/odata/v4.0/os/vocabularies/Org.OData.Core.V1.xml"> + <edmx:Include Alias="Core" Namespace="Org.OData.Core.V1" /> + </edmx:Reference> <edmx:DataServices> <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="Org.OData.AnnoatationTest" Alias="test"> http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d880d6c4/lib/server-core-ext/src/test/resources/trippin.xml ---------------------------------------------------------------------- diff --git a/lib/server-core-ext/src/test/resources/trippin.xml b/lib/server-core-ext/src/test/resources/trippin.xml index 3266344..5970ea2 100644 --- a/lib/server-core-ext/src/test/resources/trippin.xml +++ b/lib/server-core-ext/src/test/resources/trippin.xml @@ -10,6 +10,9 @@ OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"> + <edmx:Reference Uri="org.apache.olingo.v1.xml"> + <edmx:Include Alias="olingo-extensions" Namespace="org.apache.olingo.v1" /> + </edmx:Reference> <edmx:DataServices> <Schema Namespace="Microsoft.OData.SampleService.Models.TripPin" xmlns="http://docs.oasis-open.org/odata/ns/edm"> @@ -103,7 +106,7 @@ <EnumMember>Org.OData.Core.V1.Permission/Read</EnumMember> </Annotation> </Property> - <Property Name="Name" Type="Edm.String" Nullable="false" /> + <Property Name="Name" Type="Edm.String" Nullable="false"/> <Property Name="IataCode" Type="Edm.String" Nullable="false"> <Annotation Term="Org.OData.Core.V1.Immutable" Bool="true" /> </Property> @@ -453,6 +456,7 @@ </Annotation> <Annotation Term="Core.RequiresType" String="Edm.String" /> </Term> + <Annotation Term="org.apache.olingo.v1.xml10-incompatible-char-replacement" String="xxx"/> </Schema> </edmx:DataServices> </edmx:Edmx> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d880d6c4/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializer.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializer.java index 0057db4..a72e096 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializer.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializer.java @@ -52,6 +52,7 @@ import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.commons.api.edm.constants.EdmTypeKind; import org.apache.olingo.commons.api.ex.ODataErrorDetail; import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory; +import org.apache.olingo.commons.core.edm.primitivetype.EdmString; import org.apache.olingo.server.api.ODataServerError; import org.apache.olingo.server.api.ServiceMetadata; import org.apache.olingo.server.api.serializer.ComplexSerializerOptions; @@ -250,10 +251,10 @@ public class ODataXmlSerializer extends AbstractODataSerializer { } if (options == null) { - writeEntitySet(metadata, entityType, entitySet, null, null, writer); + writeEntitySet(metadata, entityType, entitySet, null, null, null, writer); } else { writeEntitySet(metadata, entityType, entitySet, - options.getExpand(), options.getSelect(), writer); + options.getExpand(), options.getSelect(), options.xml10InvalidCharReplacement(), writer); } writer.writeEndElement(); @@ -296,7 +297,9 @@ public class ODataXmlSerializer extends AbstractODataSerializer { writer.writeStartDocument(DEFAULT_CHARSET, "1.0"); writeEntity(metadata, entityType, entity, contextURL, options == null ? null : options.getExpand(), - options == null ? null : options.getSelect(), writer, true); + options == null ? null : options.getSelect(), + options == null ? null : options.xml10InvalidCharReplacement(), + writer, true); writer.writeEndDocument(); writer.flush(); @@ -336,15 +339,17 @@ public class ODataXmlSerializer extends AbstractODataSerializer { protected void writeEntitySet(final ServiceMetadata metadata, final EdmEntityType entityType, final EntityCollection entitySet, final ExpandOption expand, final SelectOption select, - final XMLStreamWriter writer) throws XMLStreamException, SerializerException { + final String xml10InvalidCharReplacement,final XMLStreamWriter writer) + throws XMLStreamException, SerializerException { for (final Entity entity : entitySet.getEntities()) { - writeEntity(metadata, entityType, entity, null, expand, select, writer, false); + writeEntity(metadata, entityType, entity, null, expand, select, xml10InvalidCharReplacement, writer, false); } } protected void writeEntity(final ServiceMetadata metadata, final EdmEntityType entityType, final Entity entity, final ContextURL contextURL, final ExpandOption expand, - final SelectOption select, final XMLStreamWriter writer, final boolean top) + final SelectOption select, final String xml10InvalidCharReplacement, + final XMLStreamWriter writer, final boolean top) throws XMLStreamException, SerializerException { writer.writeStartElement(ATOM, Constants.ATOM_ELEM_ENTRY, NS_ATOM); @@ -397,7 +402,7 @@ public class ODataXmlSerializer extends AbstractODataSerializer { } EdmEntityType resolvedType = resolveEntityType(metadata, entityType, entity.getType()); - writeNavigationProperties(metadata, resolvedType, entity, expand, writer); + writeNavigationProperties(metadata, resolvedType, entity, expand, xml10InvalidCharReplacement, writer); writer.writeStartElement(ATOM, Constants.ATOM_ELEM_CATEGORY, NS_ATOM); writer.writeAttribute(Constants.ATOM_ATTR_SCHEME, Constants.NS_SCHEME); @@ -412,7 +417,7 @@ public class ODataXmlSerializer extends AbstractODataSerializer { } writer.writeStartElement(METADATA, Constants.PROPERTIES, NS_METADATA); - writeProperties(metadata, resolvedType, entity.getProperties(), select, writer); + writeProperties(metadata, resolvedType, entity.getProperties(), select, xml10InvalidCharReplacement, writer); writer.writeEndElement(); // properties if (!entityType.hasStream()) { // content @@ -490,8 +495,8 @@ public class ODataXmlSerializer extends AbstractODataSerializer { } protected void writeProperties(final ServiceMetadata metadata, final EdmStructuredType type, - final List<Property> properties, final SelectOption select, final XMLStreamWriter writer) - throws XMLStreamException, SerializerException { + final List<Property> properties, final SelectOption select, final String xml10InvalidCharReplacement, + final XMLStreamWriter writer) throws XMLStreamException, SerializerException { final boolean all = ExpandSelectHelper.isAll(select); final Set<String> selected = all ? new HashSet<String>() : ExpandSelectHelper.getSelectedPropertyNames(select.getSelectItems()); @@ -501,14 +506,15 @@ public class ODataXmlSerializer extends AbstractODataSerializer { final Property property = findProperty(propertyName, properties); final Set<List<String>> selectedPaths = all || edmProperty.isPrimitive() ? null : ExpandSelectHelper.getSelectedPaths(select.getSelectItems(), propertyName); - writeProperty(metadata, edmProperty, property, selectedPaths, writer); + writeProperty(metadata, edmProperty, property, selectedPaths, xml10InvalidCharReplacement, writer); } } } protected void writeNavigationProperties(final ServiceMetadata metadata, final EdmStructuredType type, final Linked linked, final ExpandOption expand, - final XMLStreamWriter writer) throws SerializerException, XMLStreamException { + final String xml10InvalidCharReplacement, final XMLStreamWriter writer) + throws SerializerException, XMLStreamException { if (ExpandSelectHelper.hasExpand(expand)) { final boolean expandAll = ExpandSelectHelper.isExpandAll(expand); final Set<String> expanded = expandAll ? new HashSet<String>() : @@ -529,7 +535,7 @@ public class ODataXmlSerializer extends AbstractODataSerializer { writeExpandedNavigationProperty(metadata, property, navigationLink, innerOptions == null ? null : innerOptions.getExpandOption(), innerOptions == null ? null : innerOptions.getSelectOption(), - writer); + xml10InvalidCharReplacement, writer); writer.writeEndElement(); writer.writeEndElement(); } @@ -588,27 +594,28 @@ public class ODataXmlSerializer extends AbstractODataSerializer { protected void writeExpandedNavigationProperty(final ServiceMetadata metadata, final EdmNavigationProperty property, final Link navigationLink, - final ExpandOption innerExpand, final SelectOption innerSelect, final XMLStreamWriter writer) - throws XMLStreamException, SerializerException { + final ExpandOption innerExpand, final SelectOption innerSelect, final String xml10InvalidCharReplacement, + final XMLStreamWriter writer) throws XMLStreamException, SerializerException { if (property.isCollection()) { if (navigationLink != null && navigationLink.getInlineEntitySet() != null) { writer.writeStartElement(ATOM, Constants.ATOM_ELEM_FEED, NS_ATOM); writeEntitySet(metadata, property.getType(), navigationLink.getInlineEntitySet(), innerExpand, - innerSelect, writer); + innerSelect, xml10InvalidCharReplacement, writer); writer.writeEndElement(); } } else { if (navigationLink != null && navigationLink.getInlineEntity() != null) { writeEntity(metadata, property.getType(), navigationLink.getInlineEntity(), null, - innerExpand, innerSelect, writer, false); + innerExpand, innerSelect, xml10InvalidCharReplacement, writer, false); } } } - protected void writeProperty(final ServiceMetadata metadata, final EdmProperty edmProperty, - final Property property, - final Set<List<String>> selectedPaths, final XMLStreamWriter writer) throws XMLStreamException, - SerializerException { + protected void writeProperty(final ServiceMetadata metadata, + final EdmProperty edmProperty, final Property property, + final Set<List<String>> selectedPaths, + final String xml10InvalidCharReplacement, final XMLStreamWriter writer) + throws XMLStreamException, SerializerException { writer.writeStartElement(DATA, edmProperty.getName(), NS_DATA); if (property == null || property.isNull()) { if (edmProperty.isNullable()) { @@ -618,7 +625,7 @@ public class ODataXmlSerializer extends AbstractODataSerializer { SerializerException.MessageKeys.MISSING_PROPERTY, edmProperty.getName()); } } else { - writePropertyValue(metadata, edmProperty, property, selectedPaths, writer); + writePropertyValue(metadata, edmProperty, property, selectedPaths, xml10InvalidCharReplacement, writer); } writer.writeEndElement(); } @@ -642,9 +649,11 @@ public class ODataXmlSerializer extends AbstractODataSerializer { return definedType; } - private void writePropertyValue(final ServiceMetadata metadata, final EdmProperty edmProperty, - final Property property, final Set<List<String>> selectedPaths, - final XMLStreamWriter writer) throws XMLStreamException, SerializerException { + private void writePropertyValue(final ServiceMetadata metadata, + final EdmProperty edmProperty, final Property property, + final Set<List<String>> selectedPaths, + final String xml10InvalidCharReplacement, final XMLStreamWriter writer) + throws XMLStreamException, SerializerException { try { if (edmProperty.isPrimitive() || edmProperty.getType().getKind() == EdmTypeKind.ENUM @@ -657,22 +666,23 @@ public class ODataXmlSerializer extends AbstractODataSerializer { writePrimitiveCollection((EdmPrimitiveType) edmProperty.getType(), property, edmProperty.isNullable(), edmProperty.getMaxLength(), edmProperty.getPrecision(), edmProperty.getScale(), edmProperty.isUnicode(), - writer); + xml10InvalidCharReplacement,writer); } else { writePrimitive((EdmPrimitiveType) edmProperty.getType(), property, edmProperty.isNullable(), edmProperty.getMaxLength(), edmProperty.getPrecision(), edmProperty.getScale(), edmProperty.isUnicode(), - writer); + xml10InvalidCharReplacement, writer); } } else if (property.isComplex()) { if (edmProperty.isCollection()) { writer.writeAttribute(METADATA, NS_METADATA, Constants.ATTR_TYPE, collectionType(edmProperty.getType())); - writeComplexCollection(metadata, (EdmComplexType) edmProperty.getType(), property, selectedPaths, writer); + writeComplexCollection(metadata, (EdmComplexType) edmProperty.getType(), property, selectedPaths, + xml10InvalidCharReplacement, writer); } else { writer.writeAttribute(METADATA, NS_METADATA, Constants.ATTR_TYPE, "#" + complexType(metadata, (EdmComplexType) edmProperty.getType(), property.getType())); writeComplexValue(metadata, property, (EdmComplexType) edmProperty.getType(), property.asComplex().getValue(), - selectedPaths, writer); + selectedPaths, xml10InvalidCharReplacement, writer); } } else { throw new SerializerException("Property type not yet supported!", @@ -687,14 +697,15 @@ public class ODataXmlSerializer extends AbstractODataSerializer { private void writePrimitiveCollection(final EdmPrimitiveType type, final Property property, final Boolean isNullable, final Integer maxLength, final Integer precision, final Integer scale, - final Boolean isUnicode, + final Boolean isUnicode, final String xml10InvalidCharReplacement, final XMLStreamWriter writer) throws XMLStreamException, EdmPrimitiveTypeException, SerializerException { for (Object value : property.asCollection()) { writer.writeStartElement(METADATA, Constants.ELEM_ELEMENT, NS_METADATA); switch (property.getValueType()) { case COLLECTION_PRIMITIVE: case COLLECTION_ENUM: - writePrimitiveValue(type, value, isNullable, maxLength, precision, scale, isUnicode, writer); + writePrimitiveValue(type, value, isNullable, maxLength, precision, + scale, isUnicode, xml10InvalidCharReplacement, writer); break; case COLLECTION_GEOSPATIAL: throw new SerializerException("Property type not yet supported!", @@ -707,8 +718,9 @@ public class ODataXmlSerializer extends AbstractODataSerializer { } } - private void writeComplexCollection(final ServiceMetadata metadata, final EdmComplexType type, - final Property property, final Set<List<String>> selectedPaths, final XMLStreamWriter writer) + private void writeComplexCollection(final ServiceMetadata metadata, + final EdmComplexType type, final Property property, final Set<List<String>> selectedPaths, + final String xml10InvalidCharReplacement, final XMLStreamWriter writer) throws XMLStreamException, SerializerException { for (Object value : property.asCollection()) { writer.writeStartElement(METADATA, Constants.ELEM_ELEMENT, NS_METADATA); @@ -717,7 +729,9 @@ public class ODataXmlSerializer extends AbstractODataSerializer { } switch (property.getValueType()) { case COLLECTION_COMPLEX: - writeComplexValue(metadata, property, type, ((ComplexValue) value).getValue(), selectedPaths, writer); + writeComplexValue(metadata, property, type, + ((ComplexValue) value).getValue(), selectedPaths, + xml10InvalidCharReplacement, writer); break; default: throw new SerializerException("Property type not yet supported!", @@ -729,7 +743,7 @@ public class ODataXmlSerializer extends AbstractODataSerializer { private void writePrimitive(final EdmPrimitiveType type, final Property property, final Boolean isNullable, final Integer maxLength, final Integer precision, final Integer scale, - final Boolean isUnicode, final XMLStreamWriter writer) + final Boolean isUnicode, final String xml10InvalidCharReplacement, final XMLStreamWriter writer) throws EdmPrimitiveTypeException, XMLStreamException, SerializerException { if (property.isPrimitive()) { if (type != EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.String)) { @@ -739,7 +753,7 @@ public class ODataXmlSerializer extends AbstractODataSerializer { type.getName()); } writePrimitiveValue(type, property.asPrimitive(), - isNullable, maxLength, precision, scale, isUnicode, writer); + isNullable, maxLength, precision, scale, isUnicode, xml10InvalidCharReplacement, writer); } else if (property.isGeospatial()) { throw new SerializerException("Property type not yet supported!", SerializerException.MessageKeys.UNSUPPORTED_PROPERTY_TYPE, property.getName()); @@ -747,7 +761,7 @@ public class ODataXmlSerializer extends AbstractODataSerializer { writer.writeAttribute(METADATA, NS_METADATA, Constants.ATTR_TYPE, "#" + type.getFullQualifiedName().getFullQualifiedNameAsString()); writePrimitiveValue(type, property.asEnum(), - isNullable, maxLength, precision, scale, isUnicode, writer); + isNullable, maxLength, precision, scale, isUnicode, xml10InvalidCharReplacement, writer); } else { throw new SerializerException("Inconsistent property type!", SerializerException.MessageKeys.INCONSISTENT_PROPERTY_TYPE, property.getName()); @@ -756,19 +770,23 @@ public class ODataXmlSerializer extends AbstractODataSerializer { protected void writePrimitiveValue(final EdmPrimitiveType type, final Object primitiveValue, final Boolean isNullable, final Integer maxLength, final Integer precision, final Integer scale, - final Boolean isUnicode, + final Boolean isUnicode, final String xml10InvalidCharReplacement, final XMLStreamWriter writer) throws EdmPrimitiveTypeException, XMLStreamException { final String value = type.valueToString(primitiveValue, isNullable, maxLength, precision, scale, isUnicode); if (value == null) { writer.writeAttribute(METADATA, NS_METADATA, Constants.ATTR_NULL, "true"); } else { - writer.writeCharacters(value); + // XML 1.0 does not handle certain unicode characters, they need to be replaced + writer.writeCharacters(replaceInvalidCharacters(type, value, + isUnicode, xml10InvalidCharReplacement)); } } - protected void writeComplexValue(final ServiceMetadata metadata, Property complexProperty, final EdmComplexType type, - final List<Property> properties, final Set<List<String>> selectedPaths, final XMLStreamWriter writer) + protected void writeComplexValue(final ServiceMetadata metadata, + Property complexProperty, final EdmComplexType type, + final List<Property> properties, final Set<List<String>> selectedPaths, + final String xml10InvalidCharReplacement, final XMLStreamWriter writer) throws XMLStreamException, SerializerException { final EdmComplexType resolvedType = resolveComplexType(metadata, @@ -779,7 +797,7 @@ public class ODataXmlSerializer extends AbstractODataSerializer { if (selectedPaths == null || ExpandSelectHelper.isSelected(selectedPaths, propertyName)) { writeProperty(metadata, (EdmProperty) resolvedType.getProperty(propertyName), property, selectedPaths == null ? null : ExpandSelectHelper.getReducedSelectedPaths(selectedPaths, propertyName), - writer); + xml10InvalidCharReplacement, writer); } } } @@ -822,6 +840,7 @@ public class ODataXmlSerializer extends AbstractODataSerializer { options == null ? null : options.getPrecision(), options == null ? null : options.getScale(), options == null ? null : options.isUnicode(), + options == null ? null : options.xml10InvalidCharReplacement(), writer); } writer.writeEndElement(); @@ -874,7 +893,10 @@ public class ODataXmlSerializer extends AbstractODataSerializer { writer.writeAttribute(METADATA, NS_METADATA, Constants.ATTR_NULL, "true"); } else { final List<Property> values = property.asComplex().getValue(); - writeProperties(metadata, resolvedType, values, options == null ? null : options.getSelect(), writer); + writeProperties(metadata, resolvedType, values, + options == null ? null : options.getSelect(), + options == null ? null : options.xml10InvalidCharReplacement(), + writer); } writer.writeEndDocument(); writer.flush(); @@ -922,6 +944,7 @@ public class ODataXmlSerializer extends AbstractODataSerializer { options == null ? null : options.getPrecision(), options == null ? null : options.getScale(), options == null ? null : options.isUnicode(), + options == null ? null : options.xml10InvalidCharReplacement(), writer); writer.writeEndElement(); writer.writeEndDocument(); @@ -967,7 +990,8 @@ public class ODataXmlSerializer extends AbstractODataSerializer { writer.writeAttribute(METADATA, NS_METADATA, Constants.CONTEXT, ContextURLBuilder.create(contextURL).toASCIIString()); writeMetadataETag(metadata, writer); - writeComplexCollection(metadata, type, property, null, writer); + writeComplexCollection(metadata, type, property, null, + options == null ? null:options.xml10InvalidCharReplacement(), writer); writer.writeEndElement(); writer.writeEndDocument(); writer.flush(); @@ -1104,4 +1128,30 @@ public class ODataXmlSerializer extends AbstractODataSerializer { writer.writeAttribute(Constants.ATTR_HREF, entitySet.getNext().toASCIIString()); writer.writeEndElement(); } + + static String replaceInvalidCharacters(EdmPrimitiveType expectedType, + String value, Boolean isUniCode, String invalidCharacterReplacement) { + if (!(expectedType instanceof EdmString) + || invalidCharacterReplacement == null || isUniCode == null || !isUniCode) { + return value; + } + String s = (String) value; + StringBuilder result = null; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c <= 0x0020 && c != ' ' && c != '\n' && c != '\t' && c != '\r') { + if (result == null) { + result = new StringBuilder(); + result.append(s.substring(0, i)); + } + result.append(invalidCharacterReplacement); + } else if (result != null) { + result.append(c); + } + } + if (result == null) { + return value; + } + return result.toString(); + } } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d880d6c4/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializerTest.java ---------------------------------------------------------------------- diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializerTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializerTest.java index c904ea7..07b5e56 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializerTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializerTest.java @@ -1796,6 +1796,30 @@ public class ODataXmlSerializerTest { } @Test + public void testXML10ReplacementChar() throws Exception { + final EdmEntitySet edmEntitySet = entityContainer.getEntitySet("ESAllPrim"); + final EdmProperty edmProperty = (EdmProperty) edmEntitySet.getEntityType().getProperty("PropertyString"); + final Property property = data.readAll(edmEntitySet).getEntities().get(0).getProperty(edmProperty.getName()); + property.setValue(ValueType.PRIMITIVE, "ab\u0000cd\u0001"); + final String resultString = IOUtils.toString(serializer + .primitive(metadata, (EdmPrimitiveType) edmProperty.getType(), property, + PrimitiveSerializerOptions.with() + .contextURL(ContextURL.with() + .entitySet(edmEntitySet).keyPath("32767").navOrPropertyPath(edmProperty.getName()) + .build()) + .xml10InvalidCharReplacement("XX") + .unicode(Boolean.TRUE) + .build()).getContent()); + + String expected = "<?xml version='1.0' encoding='UTF-8'?>" + + "<m:value xmlns:m=\"http://docs.oasis-open.org/odata/ns/metadata\" " + + "m:context=\"$metadata#ESAllPrim(32767)/PropertyString\" " + + "m:metadata-etag=\"metadataETag\">" + + "abXXcdXX</m:value>"; + Assert.assertEquals(expected, resultString); + } + + @Test public void primitivePropertyNull() throws Exception { final EdmEntitySet edmEntitySet = entityContainer.getEntitySet("ESAllPrim"); final EdmProperty edmProperty = (EdmProperty) edmEntitySet.getEntityType().getProperty("PropertyString"); @@ -1966,8 +1990,7 @@ public class ODataXmlSerializerTest { XMLAssert.assertXMLEqual(diff, true); } - private static class CustomDifferenceListener implements DifferenceListener { - + public static class CustomDifferenceListener implements DifferenceListener { @Override public int differenceFound(Difference difference) { final String xpath = "/updated[1]/text()[1]";
