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 d6788e538..179c4d4ec 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; 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 611485f9d..9ee7d247a 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; 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 16481a2ae..e24466009 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; 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 1de20d815..61a316045 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; 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 e84aaa487..c72f420a2 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; 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 03a667584..b93ef4c1f 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); - } - - protected SchemaBasedEdmProvider buildEdmProvider(Reader csdl, - ReferenceResolver referenceResolver, boolean loadCoreVocabularies) throws XMLStreamException { XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); - XMLEventReader reader = xmlInputFactory.createXMLEventReader(csdl); - return buildEdmProvider(reader, referenceResolver, loadCoreVocabularies); + XMLEventReader reader = xmlInputFactory.createXMLEventReader(csdl); + return buildEdmProvider(reader, this.referenceResolver, + this.implicitlyLoadCoreVocabularies, this.useLocalCoreVocabularies); } + protected SchemaBasedEdmProvider buildEdmProvider(Reader csdl, + ReferenceResolver resolver, boolean loadCore, boolean useLocal) + throws XMLStreamException { + XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + 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() { @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 references = new ArrayList(); new ElementReader() { @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) { 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 a778e2cda..8aea5558d 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 edmSchemas = new ArrayList(); private final Map references = new ConcurrentHashMap(); - private final Map referenceSchemas - = new ConcurrentHashMap(); - private String xmlBase; - private ReferenceResolver referenceResolver; + private final Map referenceSchemas = + new ConcurrentHashMap(); + private final Map coreVocabularySchemas = + new ConcurrentHashMap(); - 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 getReferences(){ return new ArrayList(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+"/"; - } - } } } 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 0796144ea..f6b329642 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 getSerializerOptions(Class serilizerOptions, ContextURL contextUrl, - boolean references) throws ContentNegotiatorException { + public T getSerializerOptions(Class 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; 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 42fbdeaf0..45e1ed655 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 getSerializerOptions(Class 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); } 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 000000000..d197e45c9 --- /dev/null +++ b/lib/server-core-ext/src/main/resources/org.apache.olingo.v1.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + Replacement character for invalid characters in the XML 1.0 Atom payload + + + + + + + \ No newline at end of file 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 d71c54de5..75c12ad10 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")); } 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 83160b4fe..8afe5d2b2 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")); } 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 90ead9415..ab4a62a89 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")); 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 3beb2749b..ec0c7c76b 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 = + "" + + "FM" + + "Shanghai xxxAirlinexxx" + + "" + + "" + + "" + +""; + assertTrue(actual.endsWith(expected)); } - + @Test public void testReadEntitySetWithPaging() throws Exception { String url = baseURL+"/People"; 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 06e498c01..7ab019d23 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); diff --git a/lib/server-core-ext/src/test/resources/airlines.json b/lib/server-core-ext/src/test/resources/airlines.json index 1d93aa752..9019c97bf 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", diff --git a/lib/server-core-ext/src/test/resources/annotations.xml b/lib/server-core-ext/src/test/resources/annotations.xml index 1c5281a90..3c8570f56 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. --> + + + diff --git a/lib/server-core-ext/src/test/resources/trippin.xml b/lib/server-core-ext/src/test/resources/trippin.xml index 3266344f2..5970ea2dc 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. --> + + + @@ -103,7 +106,7 @@ Org.OData.Core.V1.Permission/Read - + @@ -453,6 +456,7 @@ + \ No newline at end of file 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 0057db4aa..a72e096f1 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 properties, final SelectOption select, final XMLStreamWriter writer) - throws XMLStreamException, SerializerException { + final List properties, final SelectOption select, final String xml10InvalidCharReplacement, + final XMLStreamWriter writer) throws XMLStreamException, SerializerException { final boolean all = ExpandSelectHelper.isAll(select); final Set selected = all ? new HashSet() : ExpandSelectHelper.getSelectedPropertyNames(select.getSelectItems()); @@ -501,14 +506,15 @@ public class ODataXmlSerializer extends AbstractODataSerializer { final Property property = findProperty(propertyName, properties); final Set> 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 expanded = expandAll ? new HashSet() : @@ -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> selectedPaths, final XMLStreamWriter writer) throws XMLStreamException, - SerializerException { + protected void writeProperty(final ServiceMetadata metadata, + final EdmProperty edmProperty, final Property property, + final Set> 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> selectedPaths, - final XMLStreamWriter writer) throws XMLStreamException, SerializerException { + private void writePropertyValue(final ServiceMetadata metadata, + final EdmProperty edmProperty, final Property property, + final Set> 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> selectedPaths, final XMLStreamWriter writer) + private void writeComplexCollection(final ServiceMetadata metadata, + final EdmComplexType type, final Property property, final Set> 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 properties, final Set> selectedPaths, final XMLStreamWriter writer) + protected void writeComplexValue(final ServiceMetadata metadata, + Property complexProperty, final EdmComplexType type, + final List properties, final Set> 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 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(); + } } 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 c904ea726..07b5e569a 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 @@ -1795,6 +1795,30 @@ public class ODataXmlSerializerTest { Assert.assertEquals(expected, resultString); } + @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 = "" + + "" + + "abXXcdXX"; + Assert.assertEquals(expected, resultString); + } + @Test public void primitivePropertyNull() throws Exception { final EdmEntitySet edmEntitySet = entityContainer.getEntitySet("ESAllPrim"); @@ -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]";