OLINGO-878: Adding support to remove unvalid xml characters from Atom payload

This commit is contained in:
Ramesh Reddy 2016-02-11 14:12:47 -06:00
parent 3c205f907e
commit d880d6c480
20 changed files with 499 additions and 170 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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<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) {

View File

@ -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+"/";
}
}
}
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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>

View File

@ -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"));
}

View File

@ -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"));
}

View File

@ -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"));

View File

@ -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";

View File

@ -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);

View File

@ -10,7 +10,7 @@
},
{
"AirlineCode":"FM",
"Name":"Shanghai Airline"
"Name":"Shanghai \u0000Airline\u0001"
},
{
"AirlineCode":"MU",

View File

@ -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">

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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 = "<?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");
@ -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]";