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 ContextURL contextURL;
private ExpandOption expand; private ExpandOption expand;
private SelectOption select; private SelectOption select;
private String xml10InvalidCharReplacement;
/** Gets the {@link ContextURL}. */ /** Gets the {@link ContextURL}. */
public ContextURL getContextURL() { public ContextURL getContextURL() {
@ -44,6 +45,11 @@ public class ComplexSerializerOptions {
return select; return select;
} }
/** Gets the replacement string for unicode characters, that is not allowed in XML 1.0 */
public String xml10InvalidCharReplacement() {
return xml10InvalidCharReplacement;
}
private ComplexSerializerOptions() {} private ComplexSerializerOptions() {}
/** Initializes the options builder. */ /** Initializes the options builder. */
@ -78,6 +84,12 @@ public class ComplexSerializerOptions {
return this; 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. */ /** Builds the OData serializer options. */
public ComplexSerializerOptions build() { public ComplexSerializerOptions build() {
return options; return options;

View File

@ -32,6 +32,7 @@ public class EntityCollectionSerializerOptions {
private SelectOption select; private SelectOption select;
private boolean writeOnlyReferences; private boolean writeOnlyReferences;
private String id; private String id;
private String xml10InvalidCharReplacement;
/** Gets the {@link ContextURL}. */ /** Gets the {@link ContextURL}. */
public ContextURL getContextURL() { public ContextURL getContextURL() {
@ -63,6 +64,11 @@ public class EntityCollectionSerializerOptions {
return id; 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. */ /** Initializes the options builder. */
public static Builder with() { public static Builder with() {
return new Builder(); return new Builder();
@ -113,6 +119,12 @@ public class EntityCollectionSerializerOptions {
return this; 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. */ /** Builds the OData serializer options. */
public EntityCollectionSerializerOptions build() { public EntityCollectionSerializerOptions build() {
return options; return options;

View File

@ -28,6 +28,7 @@ public class EntitySerializerOptions {
private ExpandOption expand; private ExpandOption expand;
private SelectOption select; private SelectOption select;
private boolean writeOnlyReferences; private boolean writeOnlyReferences;
private String xml10InvalidCharReplacement;
/** Gets the {@link ContextURL}. */ /** Gets the {@link ContextURL}. */
public ContextURL getContextURL() { public ContextURL getContextURL() {
@ -49,6 +50,11 @@ public class EntitySerializerOptions {
return writeOnlyReferences; return writeOnlyReferences;
} }
/** Gets the replacement string for unicode characters, that is not allowed in XML 1.0 */
public String xml10InvalidCharReplacement() {
return xml10InvalidCharReplacement;
}
private EntitySerializerOptions() {} private EntitySerializerOptions() {}
/** Initializes the options builder. */ /** Initializes the options builder. */
@ -89,6 +95,12 @@ public class EntitySerializerOptions {
return this; 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. */ /** Builds the OData serializer options. */
public EntitySerializerOptions build() { public EntitySerializerOptions build() {
return options; return options;

View File

@ -30,6 +30,7 @@ public final class PrimitiveSerializerOptions {
private Integer precision; private Integer precision;
private Integer scale; private Integer scale;
private Boolean isUnicode; private Boolean isUnicode;
private String xml10InvalidCharReplacement;
/** Gets the {@link ContextURL}. */ /** Gets the {@link ContextURL}. */
public ContextURL getContextURL() { public ContextURL getContextURL() {
@ -61,6 +62,12 @@ public final class PrimitiveSerializerOptions {
return 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() {} private PrimitiveSerializerOptions() {}
/** Initializes the options builder. */ /** Initializes the options builder. */
@ -123,6 +130,12 @@ public final class PrimitiveSerializerOptions {
return this; 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. */ /** Builds the OData serializer options. */
public PrimitiveSerializerOptions build() { public PrimitiveSerializerOptions build() {
return options; return options;

View File

@ -28,6 +28,7 @@ public class PrimitiveValueSerializerOptions {
private Integer precision; private Integer precision;
private Integer scale; private Integer scale;
private Boolean isUnicode; private Boolean isUnicode;
private String xml10InvalidCharReplacement;
/** Gets the nullable facet. */ /** Gets the nullable facet. */
public Boolean isNullable() { public Boolean isNullable() {
@ -54,6 +55,11 @@ public class PrimitiveValueSerializerOptions {
return 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() {} private PrimitiveValueSerializerOptions() {}
/** Initializes the options builder. */ /** Initializes the options builder. */
@ -110,6 +116,12 @@ public class PrimitiveValueSerializerOptions {
return this; 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. */ /** Builds the OData serializer options. */
public PrimitiveValueSerializerOptions build() { public PrimitiveValueSerializerOptions build() {
return options; return options;

View File

@ -97,57 +97,93 @@ import org.apache.olingo.server.api.edmx.EdmxReferenceIncludeAnnotation;
public class MetadataParser { public class MetadataParser {
private boolean parseAnnotations = false; private boolean parseAnnotations = false;
private static final String XML_LINK_NS = "http://www.w3.org/1999/xlink"; private static final String XML_LINK_NS = "http://www.w3.org/1999/xlink";
private ReferenceResolver defaultReferenceResolver = new DefaultReferenceResolver(); private ReferenceResolver referenceResolver = new DefaultReferenceResolver();
private boolean loadCoreVocabularies = false; private boolean useLocalCoreVocabularies = true;
private boolean implicitlyLoadCoreVocabularies = false;
/**
* Avoid reading the annotations in the $metadata
* @param parse
* @return
*/
public MetadataParser parseAnnotations(boolean parse) { public MetadataParser parseAnnotations(boolean parse) {
this.parseAnnotations = parse; this.parseAnnotations = parse;
return this; return this;
} }
/**
* Externalize the reference loading, such that they can be loaded from local caches
* @param resolver
* @return
*/
public MetadataParser referenceResolver(ReferenceResolver resolver) { public MetadataParser referenceResolver(ReferenceResolver resolver) {
this.defaultReferenceResolver = resolver; this.referenceResolver = resolver;
return this; 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; return this;
} }
public ServiceMetadata buildServiceMetadata(Reader csdl) throws XMLStreamException { public ServiceMetadata buildServiceMetadata(Reader csdl) throws XMLStreamException {
SchemaBasedEdmProvider provider = buildEdmProvider(csdl, SchemaBasedEdmProvider provider = buildEdmProvider(csdl, this.referenceResolver,
this.defaultReferenceResolver, this.loadCoreVocabularies); this.implicitlyLoadCoreVocabularies, this.useLocalCoreVocabularies);
return new ServiceMetadataImpl(provider, provider.getReferences(), null); return new ServiceMetadataImpl(provider, provider.getReferences(), null);
} }
public SchemaBasedEdmProvider buildEdmProvider(Reader csdl) throws XMLStreamException { public SchemaBasedEdmProvider buildEdmProvider(Reader csdl) throws XMLStreamException {
return buildEdmProvider(csdl, this.defaultReferenceResolver, this.loadCoreVocabularies); XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
XMLEventReader reader = xmlInputFactory.createXMLEventReader(csdl);
return buildEdmProvider(reader, this.referenceResolver,
this.implicitlyLoadCoreVocabularies, this.useLocalCoreVocabularies);
} }
protected SchemaBasedEdmProvider buildEdmProvider(Reader csdl, protected SchemaBasedEdmProvider buildEdmProvider(Reader csdl,
ReferenceResolver referenceResolver, boolean loadCoreVocabularies) throws XMLStreamException { ReferenceResolver resolver, boolean loadCore, boolean useLocal)
throws XMLStreamException {
XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
XMLEventReader reader = xmlInputFactory.createXMLEventReader(csdl); XMLEventReader reader = xmlInputFactory.createXMLEventReader(csdl);
return buildEdmProvider(reader, referenceResolver, loadCoreVocabularies); return buildEdmProvider(reader, resolver, loadCore, useLocal);
} }
protected SchemaBasedEdmProvider buildEdmProvider(InputStream csdl, protected SchemaBasedEdmProvider buildEdmProvider(InputStream csdl,
ReferenceResolver referenceResolver, boolean loadCoreVocabularies) throws XMLStreamException { ReferenceResolver resolver, boolean loadCore, boolean useLocal)
throws XMLStreamException {
XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
XMLEventReader reader = xmlInputFactory.createXMLEventReader(csdl); XMLEventReader reader = xmlInputFactory.createXMLEventReader(csdl);
return buildEdmProvider(reader, referenceResolver, loadCoreVocabularies); return buildEdmProvider(reader, resolver, loadCore, useLocal);
} }
protected SchemaBasedEdmProvider buildEdmProvider(XMLEventReader reader, protected SchemaBasedEdmProvider buildEdmProvider(XMLEventReader reader,
ReferenceResolver referenceResolver, boolean loadCoreVocabularies) throws XMLStreamException { ReferenceResolver resolver, boolean loadCore, boolean useLocal)
SchemaBasedEdmProvider provider = new SchemaBasedEdmProvider(referenceResolver); throws XMLStreamException {
SchemaBasedEdmProvider provider = new SchemaBasedEdmProvider();
final StringBuilder xmlBase = new StringBuilder();
new ElementReader<SchemaBasedEdmProvider>() { new ElementReader<SchemaBasedEdmProvider>() {
@Override @Override
void build(XMLEventReader reader, StartElement element, SchemaBasedEdmProvider provider, void build(XMLEventReader reader, StartElement element, SchemaBasedEdmProvider provider,
String name) throws XMLStreamException { String name) throws XMLStreamException {
String xmlBase = attrNS(element, XML_LINK_NS, "base"); if (attrNS(element, XML_LINK_NS, "base") != null) {
provider.setXMLBase(xmlBase); xmlBase.append(attrNS(element, XML_LINK_NS, "base"));
}
String version = attr(element, "Version"); String version = attr(element, "Version");
if ("4.0".equals(version)) { if ("4.0".equals(version)) {
readDataServicesAndReference(reader, element, provider); readDataServicesAndReference(reader, element, provider);
@ -167,23 +203,100 @@ public class MetadataParser {
event.asEndElement().getName().getLocalPart())); event.asEndElement().getName().getLocalPart()));
} }
// load the core vocabularies //load core vocabularies even though they are not defined in the references
if (loadCoreVocabularies) { if (loadCore) {
loadVocabularySchema(provider, "Org.OData.Core.V1", "Org.OData.Core.V1.xml"); loadCoreVocabulary(provider, "Org.OData.Core.V1");
loadVocabularySchema(provider, "Org.OData.Capabilities.V1", "Org.OData.Capabilities.V1.xml"); loadCoreVocabulary(provider, "Org.OData.Capabilities.V1");
loadVocabularySchema(provider, "Org.OData.Measures.V1", "Org.OData.Measures.V1.xml"); 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; 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 { String resource) throws XMLStreamException {
CsdlSchema schema = provider.getSchema(namespace, false); CsdlSchema schema = provider.getVocabularySchema(namespace);
if (schema == null) { if (schema == null) {
InputStream is = this.getClass().getClassLoader().getResourceAsStream(resource); InputStream is = this.getClass().getClassLoader().getResourceAsStream(resource);
if (is != null) { if (is != null) {
SchemaBasedEdmProvider childProvider = buildEdmProvider(is, null, false); SchemaBasedEdmProvider childProvider = buildEdmProvider(is, null, false, false);
provider.addSchema(childProvider.getSchema(namespace, false)); provider.addVocabularySchema(namespace, childProvider);
} else { } else {
throw new XMLStreamException("failed to load "+resource+" core vocabulary"); throw new XMLStreamException("failed to load "+resource+" core vocabulary");
} }
@ -191,7 +304,8 @@ public class MetadataParser {
} }
private void readDataServicesAndReference(XMLEventReader reader, private void readDataServicesAndReference(XMLEventReader reader,
StartElement element, SchemaBasedEdmProvider provider) throws XMLStreamException { StartElement element, SchemaBasedEdmProvider provider)
throws XMLStreamException {
final ArrayList<EdmxReference> references = new ArrayList<EdmxReference>(); final ArrayList<EdmxReference> references = new ArrayList<EdmxReference>();
new ElementReader<SchemaBasedEdmProvider>() { new ElementReader<SchemaBasedEdmProvider>() {
@Override @Override
@ -384,7 +498,9 @@ public class MetadataParser {
CsdlTypeDefinition td = new CsdlTypeDefinition(); CsdlTypeDefinition td = new CsdlTypeDefinition();
td.setName(attr(element, "Name")); td.setName(attr(element, "Name"));
td.setUnderlyingType(new FullQualifiedName(attr(element, "UnderlyingType"))); td.setUnderlyingType(new FullQualifiedName(attr(element, "UnderlyingType")));
if (attr(element, "Unicode") != null) {
td.setUnicode(Boolean.parseBoolean(attr(element, "Unicode"))); td.setUnicode(Boolean.parseBoolean(attr(element, "Unicode")));
}
String maxLength = attr(element, "MaxLength"); String maxLength = attr(element, "MaxLength");
if (maxLength != null) { if (maxLength != null) {
@ -837,7 +953,9 @@ public class MetadataParser {
property.setCollection(isCollectionType(element)); property.setCollection(isCollectionType(element));
property.setNullable(Boolean.parseBoolean(attr(element, "Nullable") == null ? "true" : attr( property.setNullable(Boolean.parseBoolean(attr(element, "Nullable") == null ? "true" : attr(
element, "Nullable"))); element, "Nullable")));
if (attr(element, "Unicode") != null) {
property.setUnicode(Boolean.parseBoolean(attr(element, "Unicode"))); property.setUnicode(Boolean.parseBoolean(attr(element, "Unicode")));
}
String maxLength = attr(element, "MaxLength"); String maxLength = attr(element, "MaxLength");
if (maxLength != null) { if (maxLength != null) {
@ -1077,18 +1195,23 @@ public class MetadataParser {
private static class DefaultReferenceResolver implements ReferenceResolver { private static class DefaultReferenceResolver implements ReferenceResolver {
@Override @Override
public InputStream resolveReference(URI referenceUri, String xmlBase) { public InputStream resolveReference(URI referenceUri, String xmlBase) {
URL schemaURL = null; InputStream in = null;
try { try {
if (referenceUri.isAbsolute()) { if (referenceUri.isAbsolute()) {
schemaURL = referenceUri.toURL(); URL schemaURL = referenceUri.toURL();
in = schemaURL.openStream();
} else { } else {
if (xmlBase != null) { if (xmlBase != null) {
schemaURL = new URL(xmlBase+referenceUri.toString()); URL schemaURL = new URL(xmlBase+referenceUri.toString());
in = schemaURL.openStream();
} else { } else {
in = this.getClass().getClassLoader().getResourceAsStream(referenceUri.getPath());
if (in == null) {
throw new EdmException("No xml:base set to read the references from the metadata"); throw new EdmException("No xml:base set to read the references from the metadata");
} }
} }
return schemaURL.openStream(); }
return in;
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
throw new EdmException(e); throw new EdmException(e);
} catch (IOException e) { } catch (IOException e) {

View File

@ -18,15 +18,11 @@
*/ */
package org.apache.olingo.server.core; package org.apache.olingo.server.core;
import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; 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.FullQualifiedName;
import org.apache.olingo.commons.api.edm.provider.CsdlAction; import org.apache.olingo.commons.api.edm.provider.CsdlAction;
import org.apache.olingo.commons.api.edm.provider.CsdlActionImport; import org.apache.olingo.commons.api.edm.provider.CsdlActionImport;
@ -52,14 +48,10 @@ import org.apache.olingo.server.api.edmx.EdmxReferenceInclude;
public class SchemaBasedEdmProvider implements CsdlEdmProvider { public class SchemaBasedEdmProvider implements CsdlEdmProvider {
private final List<CsdlSchema> edmSchemas = new ArrayList<CsdlSchema>(); private final List<CsdlSchema> edmSchemas = new ArrayList<CsdlSchema>();
private final Map<String, EdmxReference> references = new ConcurrentHashMap<String, EdmxReference>(); private final Map<String, EdmxReference> references = new ConcurrentHashMap<String, EdmxReference>();
private final Map<String, SchemaBasedEdmProvider> referenceSchemas private final Map<String, SchemaBasedEdmProvider> referenceSchemas =
= new ConcurrentHashMap<String, SchemaBasedEdmProvider>(); new ConcurrentHashMap<String, SchemaBasedEdmProvider>();
private String xmlBase; private final Map<String, SchemaBasedEdmProvider> coreVocabularySchemas =
private ReferenceResolver referenceResolver; new ConcurrentHashMap<String, SchemaBasedEdmProvider>();
public SchemaBasedEdmProvider(ReferenceResolver referenceResolver) {
this.referenceResolver = referenceResolver;
}
void addSchema(CsdlSchema schema) { void addSchema(CsdlSchema schema) {
this.edmSchemas.add(schema); this.edmSchemas.add(schema);
@ -69,6 +61,22 @@ public class SchemaBasedEdmProvider implements CsdlEdmProvider {
return new ArrayList<EdmxReference>(references.values()); 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) { CsdlSchema getSchema(String ns) {
return getSchema(ns, true); return getSchema(ns, true);
} }
@ -79,46 +87,20 @@ public class SchemaBasedEdmProvider implements CsdlEdmProvider {
return s; return s;
} }
} }
CsdlSchema s = null;
if (checkReferences) { 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) { if (ns == null) {
return 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) { if (this.referenceSchemas.get(ns) != null) {
return this.referenceSchemas.get(ns).getSchema(ns); return this.referenceSchemas.get(ns).getSchema(ns);
@ -403,14 +385,4 @@ 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 java.util.StringTokenizer;
import org.apache.olingo.commons.api.data.ContextURL; 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.format.ContentType;
import org.apache.olingo.commons.api.http.HttpHeader; import org.apache.olingo.commons.api.http.HttpHeader;
import org.apache.olingo.commons.api.http.HttpMethod; import org.apache.olingo.commons.api.http.HttpMethod;
import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.OData;
import org.apache.olingo.server.api.ODataApplicationException; 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.ODataRequest;
import org.apache.olingo.server.api.ODataResponse; 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.ServiceMetadata;
import org.apache.olingo.server.api.serializer.ComplexSerializerOptions; import org.apache.olingo.server.api.serializer.ComplexSerializerOptions;
import org.apache.olingo.server.api.serializer.CustomContentTypeSupport; import org.apache.olingo.server.api.serializer.CustomContentTypeSupport;
@ -140,26 +144,60 @@ public abstract class ServiceRequest {
return this.request.getMethod() == HttpMethod.POST; 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") @SuppressWarnings("unchecked")
public <T> T getSerializerOptions(Class<T> serilizerOptions, ContextURL contextUrl, public <T> T getSerializerOptions(Class<T> serilizerOptions,
boolean references) throws ContentNegotiatorException { 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)) { if (serilizerOptions.isAssignableFrom(EntitySerializerOptions.class)) {
return (T) EntitySerializerOptions.with() return (T) EntitySerializerOptions.with()
.contextURL(isODataMetadataNone(getResponseContentType()) ? null : contextUrl) .contextURL(isODataMetadataNone(getResponseContentType()) ? null : contextUrl)
.expand(uriInfo.getExpandOption()).select(this.uriInfo.getSelectOption()) .expand(uriInfo.getExpandOption()).select(this.uriInfo.getSelectOption())
.writeOnlyReferences(references).build(); .writeOnlyReferences(references)
.xml10InvalidCharReplacement(xmlReplacement)
.build();
} else if (serilizerOptions.isAssignableFrom(EntityCollectionSerializerOptions.class)) { } else if (serilizerOptions.isAssignableFrom(EntityCollectionSerializerOptions.class)) {
return (T) EntityCollectionSerializerOptions.with() return (T) EntityCollectionSerializerOptions.with()
.contextURL(isODataMetadataNone(getResponseContentType()) ? null : contextUrl) .contextURL(isODataMetadataNone(getResponseContentType()) ? null : contextUrl)
.count(uriInfo.getCountOption()).expand(uriInfo.getExpandOption()) .count(uriInfo.getCountOption()).expand(uriInfo.getExpandOption())
.select(uriInfo.getSelectOption()).writeOnlyReferences(references) .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)) { } else if (serilizerOptions.isAssignableFrom(ComplexSerializerOptions.class)) {
return (T) ComplexSerializerOptions.with().contextURL(contextUrl) 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)) { } else if (serilizerOptions.isAssignableFrom(PrimitiveSerializerOptions.class)) {
return (T) PrimitiveSerializerOptions.with().contextURL(contextUrl) return (T) PrimitiveSerializerOptions.with().contextURL(contextUrl)
.xml10InvalidCharReplacement(xmlReplacement)
.build(); .build();
} }
return null; return null;

View File

@ -250,8 +250,17 @@ public class DataRequest extends ServiceRequest {
public <T> T getSerializerOptions(Class<T> serilizerOptions, ContextURL contextUrl, boolean references) public <T> T getSerializerOptions(Class<T> serilizerOptions, ContextURL contextUrl, boolean references)
throws ContentNegotiatorException { throws ContentNegotiatorException {
if (serilizerOptions.isAssignableFrom(PrimitiveSerializerOptions.class)) { 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) return (T) PrimitiveSerializerOptions.with().contextURL(contextUrl)
.facetsFrom(getUriResourceProperty().getProperty()).build(); .facetsFrom(getUriResourceProperty().getProperty())
.xml10InvalidCharReplacement(xmlReplacement)
.build();
} }
return super.getSerializerOptions(serilizerOptions, contextUrl, references); 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 { public void setUp() throws Exception {
MetadataParser parser = new MetadataParser(); MetadataParser parser = new MetadataParser();
parser.parseAnnotations(true); parser.parseAnnotations(true);
parser.loadCoreVocabularies(true); parser.useLocalCoreVocabularies(true);
provider = (CsdlEdmProvider) parser.buildEdmProvider(new FileReader("src/test/resources/annotations.xml")); provider = (CsdlEdmProvider) parser.buildEdmProvider(new FileReader("src/test/resources/annotations.xml"));
} }

View File

@ -54,7 +54,6 @@ public class MetadataParserTest {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
MetadataParser parser = new MetadataParser(); MetadataParser parser = new MetadataParser();
parser.parseAnnotations(true);
provider = (CsdlEdmProvider) parser.buildEdmProvider(new FileReader("src/test/resources/trippin.xml")); 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 { public void beforeTest(ServiceHandler serviceHandler) throws Exception {
MetadataParser parser = new MetadataParser(); MetadataParser parser = new MetadataParser();
parser.parseAnnotations(true);
parser.useLocalCoreVocabularies(true);
parser.implicitlyLoadCoreVocabularies(true);
ServiceMetadata metadata = parser.buildServiceMetadata(new FileReader("src/test/resources/trippin.xml")); ServiceMetadata metadata = parser.buildServiceMetadata(new FileReader("src/test/resources/trippin.xml"));
File baseDir = new File(System.getProperty("java.io.tmpdir")); File baseDir = new File(System.getProperty("java.io.tmpdir"));

View File

@ -114,18 +114,21 @@ public class TripPinServiceTest {
} }
@Test @Test
public void testEntitySet() throws Exception { public void testXMLInvalidChars() throws Exception {
HttpRequest req = new HttpGet(baseURL+"/People"); HttpRequest req = new HttpGet(baseURL+"/Airlines('FM')");
req.setHeader("Content-Type", "application/json;odata.metadata=minimal"); req.setHeader("Accept", "application/xml");
HttpResponse response = httpSend(req, 200); HttpResponse response = httpSend(req, 200);
JsonNode node = getJSONNode(response); String actual = IOUtils.toString(response.getEntity().getContent());
String expected =
assertEquals("$metadata#People", node.get("@odata.context").asText()); "<m:properties>"
assertEquals(baseURL+"/People?$skiptoken=8", node.get("@odata.nextLink").asText()); + "<d:AirlineCode>FM</d:AirlineCode>"
+ "<d:Name>Shanghai xxxAirlinexxx</d:Name>"
JsonNode person = ((ArrayNode)node.get("value")).get(0); + "<d:Picture m:null=\"true\"/>"
assertEquals("russellwhyte", person.get("UserName").asText()); + "</m:properties>"
+ "</a:content>"
+"</a:entry>";
assertTrue(actual.endsWith(expected));
} }
@Test @Test

View File

@ -51,7 +51,8 @@ public class TripPinServlet extends HttpServlet {
try { try {
parser.parseAnnotations(true); parser.parseAnnotations(true);
parser.loadCoreVocabularies(true); parser.useLocalCoreVocabularies(true);
parser.implicitlyLoadCoreVocabularies(true);
metadata = parser.buildServiceMetadata(new FileReader("src/test/resources/trippin.xml")); metadata = parser.buildServiceMetadata(new FileReader("src/test/resources/trippin.xml"));
} catch (XMLStreamException e) { } catch (XMLStreamException e) {
throw new IOException(e); throw new IOException(e);

View File

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

View File

@ -11,6 +11,9 @@
language governing permissions and limitations under the License. --> language governing permissions and limitations under the License. -->
<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" <edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"
Version="4.0"> 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> <edmx:DataServices>
<Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm"
Namespace="Org.OData.AnnoatationTest" Alias="test"> 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 OF ANY KIND, either express or implied. See the License for the specific
language governing permissions and limitations under the License. --> language governing permissions and limitations under the License. -->
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"> <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> <edmx:DataServices>
<Schema Namespace="Microsoft.OData.SampleService.Models.TripPin" <Schema Namespace="Microsoft.OData.SampleService.Models.TripPin"
xmlns="http://docs.oasis-open.org/odata/ns/edm"> xmlns="http://docs.oasis-open.org/odata/ns/edm">
@ -103,7 +106,7 @@
<EnumMember>Org.OData.Core.V1.Permission/Read</EnumMember> <EnumMember>Org.OData.Core.V1.Permission/Read</EnumMember>
</Annotation> </Annotation>
</Property> </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"> <Property Name="IataCode" Type="Edm.String" Nullable="false">
<Annotation Term="Org.OData.Core.V1.Immutable" Bool="true" /> <Annotation Term="Org.OData.Core.V1.Immutable" Bool="true" />
</Property> </Property>
@ -453,6 +456,7 @@
</Annotation> </Annotation>
<Annotation Term="Core.RequiresType" String="Edm.String" /> <Annotation Term="Core.RequiresType" String="Edm.String" />
</Term> </Term>
<Annotation Term="org.apache.olingo.v1.xml10-incompatible-char-replacement" String="xxx"/>
</Schema> </Schema>
</edmx:DataServices> </edmx:DataServices>
</edmx:Edmx> </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.edm.constants.EdmTypeKind;
import org.apache.olingo.commons.api.ex.ODataErrorDetail; 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.EdmPrimitiveTypeFactory;
import org.apache.olingo.commons.core.edm.primitivetype.EdmString;
import org.apache.olingo.server.api.ODataServerError; import org.apache.olingo.server.api.ODataServerError;
import org.apache.olingo.server.api.ServiceMetadata; import org.apache.olingo.server.api.ServiceMetadata;
import org.apache.olingo.server.api.serializer.ComplexSerializerOptions; import org.apache.olingo.server.api.serializer.ComplexSerializerOptions;
@ -250,10 +251,10 @@ public class ODataXmlSerializer extends AbstractODataSerializer {
} }
if (options == null) { if (options == null) {
writeEntitySet(metadata, entityType, entitySet, null, null, writer); writeEntitySet(metadata, entityType, entitySet, null, null, null, writer);
} else { } else {
writeEntitySet(metadata, entityType, entitySet, writeEntitySet(metadata, entityType, entitySet,
options.getExpand(), options.getSelect(), writer); options.getExpand(), options.getSelect(), options.xml10InvalidCharReplacement(), writer);
} }
writer.writeEndElement(); writer.writeEndElement();
@ -296,7 +297,9 @@ public class ODataXmlSerializer extends AbstractODataSerializer {
writer.writeStartDocument(DEFAULT_CHARSET, "1.0"); writer.writeStartDocument(DEFAULT_CHARSET, "1.0");
writeEntity(metadata, entityType, entity, contextURL, writeEntity(metadata, entityType, entity, contextURL,
options == null ? null : options.getExpand(), 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.writeEndDocument();
writer.flush(); writer.flush();
@ -336,15 +339,17 @@ public class ODataXmlSerializer extends AbstractODataSerializer {
protected void writeEntitySet(final ServiceMetadata metadata, final EdmEntityType entityType, protected void writeEntitySet(final ServiceMetadata metadata, final EdmEntityType entityType,
final EntityCollection entitySet, final ExpandOption expand, final SelectOption select, 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()) { 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, protected void writeEntity(final ServiceMetadata metadata, final EdmEntityType entityType,
final Entity entity, final ContextURL contextURL, final ExpandOption expand, 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 { throws XMLStreamException, SerializerException {
writer.writeStartElement(ATOM, Constants.ATOM_ELEM_ENTRY, NS_ATOM); 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()); 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.writeStartElement(ATOM, Constants.ATOM_ELEM_CATEGORY, NS_ATOM);
writer.writeAttribute(Constants.ATOM_ATTR_SCHEME, Constants.NS_SCHEME); 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); 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 writer.writeEndElement(); // properties
if (!entityType.hasStream()) { // content if (!entityType.hasStream()) { // content
@ -490,8 +495,8 @@ public class ODataXmlSerializer extends AbstractODataSerializer {
} }
protected void writeProperties(final ServiceMetadata metadata, final EdmStructuredType type, protected void writeProperties(final ServiceMetadata metadata, final EdmStructuredType type,
final List<Property> properties, final SelectOption select, final XMLStreamWriter writer) final List<Property> properties, final SelectOption select, final String xml10InvalidCharReplacement,
throws XMLStreamException, SerializerException { final XMLStreamWriter writer) throws XMLStreamException, SerializerException {
final boolean all = ExpandSelectHelper.isAll(select); final boolean all = ExpandSelectHelper.isAll(select);
final Set<String> selected = all ? new HashSet<String>() : final Set<String> selected = all ? new HashSet<String>() :
ExpandSelectHelper.getSelectedPropertyNames(select.getSelectItems()); ExpandSelectHelper.getSelectedPropertyNames(select.getSelectItems());
@ -501,14 +506,15 @@ public class ODataXmlSerializer extends AbstractODataSerializer {
final Property property = findProperty(propertyName, properties); final Property property = findProperty(propertyName, properties);
final Set<List<String>> selectedPaths = all || edmProperty.isPrimitive() ? null : final Set<List<String>> selectedPaths = all || edmProperty.isPrimitive() ? null :
ExpandSelectHelper.getSelectedPaths(select.getSelectItems(), propertyName); ExpandSelectHelper.getSelectedPaths(select.getSelectItems(), propertyName);
writeProperty(metadata, edmProperty, property, selectedPaths, writer); writeProperty(metadata, edmProperty, property, selectedPaths, xml10InvalidCharReplacement, writer);
} }
} }
} }
protected void writeNavigationProperties(final ServiceMetadata metadata, protected void writeNavigationProperties(final ServiceMetadata metadata,
final EdmStructuredType type, final Linked linked, final ExpandOption expand, 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)) { if (ExpandSelectHelper.hasExpand(expand)) {
final boolean expandAll = ExpandSelectHelper.isExpandAll(expand); final boolean expandAll = ExpandSelectHelper.isExpandAll(expand);
final Set<String> expanded = expandAll ? new HashSet<String>() : final Set<String> expanded = expandAll ? new HashSet<String>() :
@ -529,7 +535,7 @@ public class ODataXmlSerializer extends AbstractODataSerializer {
writeExpandedNavigationProperty(metadata, property, navigationLink, writeExpandedNavigationProperty(metadata, property, navigationLink,
innerOptions == null ? null : innerOptions.getExpandOption(), innerOptions == null ? null : innerOptions.getExpandOption(),
innerOptions == null ? null : innerOptions.getSelectOption(), innerOptions == null ? null : innerOptions.getSelectOption(),
writer); xml10InvalidCharReplacement, writer);
writer.writeEndElement(); writer.writeEndElement();
writer.writeEndElement(); writer.writeEndElement();
} }
@ -588,27 +594,28 @@ public class ODataXmlSerializer extends AbstractODataSerializer {
protected void writeExpandedNavigationProperty(final ServiceMetadata metadata, protected void writeExpandedNavigationProperty(final ServiceMetadata metadata,
final EdmNavigationProperty property, final Link navigationLink, final EdmNavigationProperty property, final Link navigationLink,
final ExpandOption innerExpand, final SelectOption innerSelect, final XMLStreamWriter writer) final ExpandOption innerExpand, final SelectOption innerSelect, final String xml10InvalidCharReplacement,
throws XMLStreamException, SerializerException { final XMLStreamWriter writer) throws XMLStreamException, SerializerException {
if (property.isCollection()) { if (property.isCollection()) {
if (navigationLink != null && navigationLink.getInlineEntitySet() != null) { if (navigationLink != null && navigationLink.getInlineEntitySet() != null) {
writer.writeStartElement(ATOM, Constants.ATOM_ELEM_FEED, NS_ATOM); writer.writeStartElement(ATOM, Constants.ATOM_ELEM_FEED, NS_ATOM);
writeEntitySet(metadata, property.getType(), navigationLink.getInlineEntitySet(), innerExpand, writeEntitySet(metadata, property.getType(), navigationLink.getInlineEntitySet(), innerExpand,
innerSelect, writer); innerSelect, xml10InvalidCharReplacement, writer);
writer.writeEndElement(); writer.writeEndElement();
} }
} else { } else {
if (navigationLink != null && navigationLink.getInlineEntity() != null) { if (navigationLink != null && navigationLink.getInlineEntity() != null) {
writeEntity(metadata, property.getType(), 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, protected void writeProperty(final ServiceMetadata metadata,
final Property property, final EdmProperty edmProperty, final Property property,
final Set<List<String>> selectedPaths, final XMLStreamWriter writer) throws XMLStreamException, final Set<List<String>> selectedPaths,
SerializerException { final String xml10InvalidCharReplacement, final XMLStreamWriter writer)
throws XMLStreamException, SerializerException {
writer.writeStartElement(DATA, edmProperty.getName(), NS_DATA); writer.writeStartElement(DATA, edmProperty.getName(), NS_DATA);
if (property == null || property.isNull()) { if (property == null || property.isNull()) {
if (edmProperty.isNullable()) { if (edmProperty.isNullable()) {
@ -618,7 +625,7 @@ public class ODataXmlSerializer extends AbstractODataSerializer {
SerializerException.MessageKeys.MISSING_PROPERTY, edmProperty.getName()); SerializerException.MessageKeys.MISSING_PROPERTY, edmProperty.getName());
} }
} else { } else {
writePropertyValue(metadata, edmProperty, property, selectedPaths, writer); writePropertyValue(metadata, edmProperty, property, selectedPaths, xml10InvalidCharReplacement, writer);
} }
writer.writeEndElement(); writer.writeEndElement();
} }
@ -642,9 +649,11 @@ public class ODataXmlSerializer extends AbstractODataSerializer {
return definedType; return definedType;
} }
private void writePropertyValue(final ServiceMetadata metadata, final EdmProperty edmProperty, private void writePropertyValue(final ServiceMetadata metadata,
final Property property, final Set<List<String>> selectedPaths, final EdmProperty edmProperty, final Property property,
final XMLStreamWriter writer) throws XMLStreamException, SerializerException { final Set<List<String>> selectedPaths,
final String xml10InvalidCharReplacement, final XMLStreamWriter writer)
throws XMLStreamException, SerializerException {
try { try {
if (edmProperty.isPrimitive() if (edmProperty.isPrimitive()
|| edmProperty.getType().getKind() == EdmTypeKind.ENUM || edmProperty.getType().getKind() == EdmTypeKind.ENUM
@ -657,22 +666,23 @@ public class ODataXmlSerializer extends AbstractODataSerializer {
writePrimitiveCollection((EdmPrimitiveType) edmProperty.getType(), property, writePrimitiveCollection((EdmPrimitiveType) edmProperty.getType(), property,
edmProperty.isNullable(), edmProperty.getMaxLength(), edmProperty.isNullable(), edmProperty.getMaxLength(),
edmProperty.getPrecision(), edmProperty.getScale(), edmProperty.isUnicode(), edmProperty.getPrecision(), edmProperty.getScale(), edmProperty.isUnicode(),
writer); xml10InvalidCharReplacement,writer);
} else { } else {
writePrimitive((EdmPrimitiveType) edmProperty.getType(), property, writePrimitive((EdmPrimitiveType) edmProperty.getType(), property,
edmProperty.isNullable(), edmProperty.getMaxLength(), edmProperty.isNullable(), edmProperty.getMaxLength(),
edmProperty.getPrecision(), edmProperty.getScale(), edmProperty.isUnicode(), edmProperty.getPrecision(), edmProperty.getScale(), edmProperty.isUnicode(),
writer); xml10InvalidCharReplacement, writer);
} }
} else if (property.isComplex()) { } else if (property.isComplex()) {
if (edmProperty.isCollection()) { if (edmProperty.isCollection()) {
writer.writeAttribute(METADATA, NS_METADATA, Constants.ATTR_TYPE, collectionType(edmProperty.getType())); 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 { } else {
writer.writeAttribute(METADATA, NS_METADATA, Constants.ATTR_TYPE, writer.writeAttribute(METADATA, NS_METADATA, Constants.ATTR_TYPE,
"#" + complexType(metadata, (EdmComplexType) edmProperty.getType(), property.getType())); "#" + complexType(metadata, (EdmComplexType) edmProperty.getType(), property.getType()));
writeComplexValue(metadata, property, (EdmComplexType) edmProperty.getType(), property.asComplex().getValue(), writeComplexValue(metadata, property, (EdmComplexType) edmProperty.getType(), property.asComplex().getValue(),
selectedPaths, writer); selectedPaths, xml10InvalidCharReplacement, writer);
} }
} else { } else {
throw new SerializerException("Property type not yet supported!", 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, private void writePrimitiveCollection(final EdmPrimitiveType type, final Property property,
final Boolean isNullable, final Integer maxLength, final Integer precision, final Integer scale, 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 { final XMLStreamWriter writer) throws XMLStreamException, EdmPrimitiveTypeException, SerializerException {
for (Object value : property.asCollection()) { for (Object value : property.asCollection()) {
writer.writeStartElement(METADATA, Constants.ELEM_ELEMENT, NS_METADATA); writer.writeStartElement(METADATA, Constants.ELEM_ELEMENT, NS_METADATA);
switch (property.getValueType()) { switch (property.getValueType()) {
case COLLECTION_PRIMITIVE: case COLLECTION_PRIMITIVE:
case COLLECTION_ENUM: case COLLECTION_ENUM:
writePrimitiveValue(type, value, isNullable, maxLength, precision, scale, isUnicode, writer); writePrimitiveValue(type, value, isNullable, maxLength, precision,
scale, isUnicode, xml10InvalidCharReplacement, writer);
break; break;
case COLLECTION_GEOSPATIAL: case COLLECTION_GEOSPATIAL:
throw new SerializerException("Property type not yet supported!", 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, private void writeComplexCollection(final ServiceMetadata metadata,
final Property property, final Set<List<String>> selectedPaths, final XMLStreamWriter writer) final EdmComplexType type, final Property property, final Set<List<String>> selectedPaths,
final String xml10InvalidCharReplacement, final XMLStreamWriter writer)
throws XMLStreamException, SerializerException { throws XMLStreamException, SerializerException {
for (Object value : property.asCollection()) { for (Object value : property.asCollection()) {
writer.writeStartElement(METADATA, Constants.ELEM_ELEMENT, NS_METADATA); writer.writeStartElement(METADATA, Constants.ELEM_ELEMENT, NS_METADATA);
@ -717,7 +729,9 @@ public class ODataXmlSerializer extends AbstractODataSerializer {
} }
switch (property.getValueType()) { switch (property.getValueType()) {
case COLLECTION_COMPLEX: case COLLECTION_COMPLEX:
writeComplexValue(metadata, property, type, ((ComplexValue) value).getValue(), selectedPaths, writer); writeComplexValue(metadata, property, type,
((ComplexValue) value).getValue(), selectedPaths,
xml10InvalidCharReplacement, writer);
break; break;
default: default:
throw new SerializerException("Property type not yet supported!", 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, private void writePrimitive(final EdmPrimitiveType type, final Property property,
final Boolean isNullable, final Integer maxLength, final Integer precision, final Integer scale, 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 { throws EdmPrimitiveTypeException, XMLStreamException, SerializerException {
if (property.isPrimitive()) { if (property.isPrimitive()) {
if (type != EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.String)) { if (type != EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.String)) {
@ -739,7 +753,7 @@ public class ODataXmlSerializer extends AbstractODataSerializer {
type.getName()); type.getName());
} }
writePrimitiveValue(type, property.asPrimitive(), writePrimitiveValue(type, property.asPrimitive(),
isNullable, maxLength, precision, scale, isUnicode, writer); isNullable, maxLength, precision, scale, isUnicode, xml10InvalidCharReplacement, writer);
} else if (property.isGeospatial()) { } else if (property.isGeospatial()) {
throw new SerializerException("Property type not yet supported!", throw new SerializerException("Property type not yet supported!",
SerializerException.MessageKeys.UNSUPPORTED_PROPERTY_TYPE, property.getName()); SerializerException.MessageKeys.UNSUPPORTED_PROPERTY_TYPE, property.getName());
@ -747,7 +761,7 @@ public class ODataXmlSerializer extends AbstractODataSerializer {
writer.writeAttribute(METADATA, NS_METADATA, Constants.ATTR_TYPE, writer.writeAttribute(METADATA, NS_METADATA, Constants.ATTR_TYPE,
"#" + type.getFullQualifiedName().getFullQualifiedNameAsString()); "#" + type.getFullQualifiedName().getFullQualifiedNameAsString());
writePrimitiveValue(type, property.asEnum(), writePrimitiveValue(type, property.asEnum(),
isNullable, maxLength, precision, scale, isUnicode, writer); isNullable, maxLength, precision, scale, isUnicode, xml10InvalidCharReplacement, writer);
} else { } else {
throw new SerializerException("Inconsistent property type!", throw new SerializerException("Inconsistent property type!",
SerializerException.MessageKeys.INCONSISTENT_PROPERTY_TYPE, property.getName()); 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, protected void writePrimitiveValue(final EdmPrimitiveType type, final Object primitiveValue,
final Boolean isNullable, final Integer maxLength, final Integer precision, final Integer scale, 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 XMLStreamWriter writer) throws EdmPrimitiveTypeException, XMLStreamException {
final String value = type.valueToString(primitiveValue, final String value = type.valueToString(primitiveValue,
isNullable, maxLength, precision, scale, isUnicode); isNullable, maxLength, precision, scale, isUnicode);
if (value == null) { if (value == null) {
writer.writeAttribute(METADATA, NS_METADATA, Constants.ATTR_NULL, "true"); writer.writeAttribute(METADATA, NS_METADATA, Constants.ATTR_NULL, "true");
} else { } 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, protected void writeComplexValue(final ServiceMetadata metadata,
final List<Property> properties, final Set<List<String>> selectedPaths, final XMLStreamWriter writer) Property complexProperty, final EdmComplexType type,
final List<Property> properties, final Set<List<String>> selectedPaths,
final String xml10InvalidCharReplacement, final XMLStreamWriter writer)
throws XMLStreamException, SerializerException { throws XMLStreamException, SerializerException {
final EdmComplexType resolvedType = resolveComplexType(metadata, final EdmComplexType resolvedType = resolveComplexType(metadata,
@ -779,7 +797,7 @@ public class ODataXmlSerializer extends AbstractODataSerializer {
if (selectedPaths == null || ExpandSelectHelper.isSelected(selectedPaths, propertyName)) { if (selectedPaths == null || ExpandSelectHelper.isSelected(selectedPaths, propertyName)) {
writeProperty(metadata, (EdmProperty) resolvedType.getProperty(propertyName), property, writeProperty(metadata, (EdmProperty) resolvedType.getProperty(propertyName), property,
selectedPaths == null ? null : ExpandSelectHelper.getReducedSelectedPaths(selectedPaths, propertyName), 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.getPrecision(),
options == null ? null : options.getScale(), options == null ? null : options.getScale(),
options == null ? null : options.isUnicode(), options == null ? null : options.isUnicode(),
options == null ? null : options.xml10InvalidCharReplacement(),
writer); writer);
} }
writer.writeEndElement(); writer.writeEndElement();
@ -874,7 +893,10 @@ public class ODataXmlSerializer extends AbstractODataSerializer {
writer.writeAttribute(METADATA, NS_METADATA, Constants.ATTR_NULL, "true"); writer.writeAttribute(METADATA, NS_METADATA, Constants.ATTR_NULL, "true");
} else { } else {
final List<Property> values = property.asComplex().getValue(); 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.writeEndDocument();
writer.flush(); writer.flush();
@ -922,6 +944,7 @@ public class ODataXmlSerializer extends AbstractODataSerializer {
options == null ? null : options.getPrecision(), options == null ? null : options.getPrecision(),
options == null ? null : options.getScale(), options == null ? null : options.getScale(),
options == null ? null : options.isUnicode(), options == null ? null : options.isUnicode(),
options == null ? null : options.xml10InvalidCharReplacement(),
writer); writer);
writer.writeEndElement(); writer.writeEndElement();
writer.writeEndDocument(); writer.writeEndDocument();
@ -967,7 +990,8 @@ public class ODataXmlSerializer extends AbstractODataSerializer {
writer.writeAttribute(METADATA, NS_METADATA, Constants.CONTEXT, writer.writeAttribute(METADATA, NS_METADATA, Constants.CONTEXT,
ContextURLBuilder.create(contextURL).toASCIIString()); ContextURLBuilder.create(contextURL).toASCIIString());
writeMetadataETag(metadata, writer); writeMetadataETag(metadata, writer);
writeComplexCollection(metadata, type, property, null, writer); writeComplexCollection(metadata, type, property, null,
options == null ? null:options.xml10InvalidCharReplacement(), writer);
writer.writeEndElement(); writer.writeEndElement();
writer.writeEndDocument(); writer.writeEndDocument();
writer.flush(); writer.flush();
@ -1104,4 +1128,30 @@ public class ODataXmlSerializer extends AbstractODataSerializer {
writer.writeAttribute(Constants.ATTR_HREF, entitySet.getNext().toASCIIString()); writer.writeAttribute(Constants.ATTR_HREF, entitySet.getNext().toASCIIString());
writer.writeEndElement(); 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); 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 @Test
public void primitivePropertyNull() throws Exception { public void primitivePropertyNull() throws Exception {
final EdmEntitySet edmEntitySet = entityContainer.getEntitySet("ESAllPrim"); final EdmEntitySet edmEntitySet = entityContainer.getEntitySet("ESAllPrim");
@ -1966,8 +1990,7 @@ public class ODataXmlSerializerTest {
XMLAssert.assertXMLEqual(diff, true); XMLAssert.assertXMLEqual(diff, true);
} }
private static class CustomDifferenceListener implements DifferenceListener { public static class CustomDifferenceListener implements DifferenceListener {
@Override @Override
public int differenceFound(Difference difference) { public int differenceFound(Difference difference) {
final String xpath = "/updated[1]/text()[1]"; final String xpath = "/updated[1]/text()[1]";