You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by mi...@apache.org on 2014/10/23 10:24:07 UTC
git commit: [OLINGO-444] improved server content negotiation
Repository: olingo-odata4
Updated Branches:
refs/heads/master 5a9995570 -> 9e5b284a5
[OLINGO-444] improved server content negotiation
Change-Id: I30f53dc05eec9ff669a029f1382a37dd220abe94
Signed-off-by: Michael Bolz <mi...@sap.com>
Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/9e5b284a
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/9e5b284a
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/9e5b284a
Branch: refs/heads/master
Commit: 9e5b284a570ea20c3bc0de446cb21da1e8f73e4d
Parents: 5a99955
Author: Klaus Straubinger <kl...@sap.com>
Authored: Wed Oct 22 15:23:28 2014 +0200
Committer: Michael Bolz <mi...@sap.com>
Committed: Thu Oct 23 10:22:19 2014 +0200
----------------------------------------------------------------------
.../olingo/fit/tecsvc/client/BasicITCase.java | 6 +-
.../olingo/commons/api/format/ContentType.java | 164 +++++++------------
.../olingo/server/api/ODataHttpHandler.java | 8 +
.../olingo/server/api/ServiceMetadata.java | 2 +-
.../api/processor/CustomContentTypeSupport.java | 45 -----
.../server/api/processor/DefaultProcessor.java | 8 +-
.../api/processor/ExceptionProcessor.java | 4 +-
.../olingo/server/api/processor/Processor.java | 7 +-
.../serializer/CustomContentTypeSupport.java | 46 ++++++
.../api/serializer/RepresentationType.java | 44 +++++
.../olingo/server/core/ContentNegotiator.java | 64 ++++----
.../server/core/ContentNegotiatorException.java | 1 +
.../server/core/DefaultRedirectProcessor.java | 2 +-
.../apache/olingo/server/core/ODataHandler.java | 161 +++++++++---------
.../server/core/ODataHttpHandlerImpl.java | 8 +-
.../core/uri/parser/UriParseTreeVisitor.java | 2 +-
.../server-core-exceptions-i18n.properties | 1 +
.../server/core/ContentNegotiatorTest.java | 137 +++++-----------
.../tecsvc/processor/TechnicalProcessor.java | 32 ++--
19 files changed, 358 insertions(+), 384 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/9e5b284a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/BasicITCase.java
----------------------------------------------------------------------
diff --git a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/BasicITCase.java b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/BasicITCase.java
index 4d55013..adb1d91 100644
--- a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/BasicITCase.java
+++ b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/BasicITCase.java
@@ -61,7 +61,6 @@ import org.apache.olingo.commons.api.format.ODataFormat;
import org.apache.olingo.commons.api.http.HttpStatusCode;
import org.apache.olingo.fit.AbstractBaseTestITCase;
import org.apache.olingo.fit.tecsvc.TecSvcConst;
-import org.junit.Ignore;
import org.junit.Test;
public class BasicITCase extends AbstractBaseTestITCase {
@@ -213,7 +212,7 @@ public class BasicITCase extends AbstractBaseTestITCase {
assertNotNull(property.getPrimitiveValue());
assertEquals("Test String1", property.getPrimitiveValue().toValue());
}
-
+
@Test
public void readSimplePropertyContextURL() throws Exception {
ODataPropertyRequest<ODataProperty> request = getClient().getRetrieveRequestFactory()
@@ -228,7 +227,7 @@ public class BasicITCase extends AbstractBaseTestITCase {
"\"value\":\"Test String1\"}";
assertEquals(expectedResult, IOUtils.toString(response.getRawResponse(), "UTF-8"));
}
-
+
@Test
public void readComplexProperty() throws Exception {
ODataPropertyRequest<ODataProperty> request = getClient().getRetrieveRequestFactory()
@@ -290,7 +289,6 @@ public class BasicITCase extends AbstractBaseTestITCase {
assertEquals(HttpStatusCode.NO_CONTENT.getStatusCode(), response.getStatusCode());
}
- @Ignore("Content Negotiation!")
@Test
public void readPropertyValue() throws Exception {
final ODataValueRequest request = getClient().getRetrieveRequestFactory()
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/9e5b284a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/format/ContentType.java
----------------------------------------------------------------------
diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/format/ContentType.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/format/ContentType.java
index 60a1ce6..e2a05d1 100644
--- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/format/ContentType.java
+++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/format/ContentType.java
@@ -46,42 +46,38 @@ import java.util.TreeMap;
*/
public final class ContentType {
- public static final ContentType APPLICATION_XML = create("application/xml");
-
- public static final ContentType APPLICATION_ATOM_XML = create("application/atom+xml");
+ private static final String APPLICATION = "application";
+ private static final String TEXT = "text";
+ private static final String MULTIPART = "multipart";
+ public static final ContentType APPLICATION_XML = new ContentType(APPLICATION, "xml", null);
+ public static final ContentType APPLICATION_ATOM_XML = new ContentType(APPLICATION, "atom+xml", null);
public static final ContentType APPLICATION_ATOM_XML_ENTRY = create(APPLICATION_ATOM_XML, "type=entry");
-
public static final ContentType APPLICATION_ATOM_XML_FEED = create(APPLICATION_ATOM_XML, "type=feed");
+ public static final ContentType APPLICATION_ATOM_SVC = new ContentType(APPLICATION, "atomsvc+xml", null);
- public static final ContentType APPLICATION_ATOM_SVC = create("application/atomsvc+xml");
-
- public static final ContentType APPLICATION_JSON = create("application/json");
-
- public static final ContentType APPLICATION_OCTET_STREAM = create("application/octet-stream");
-
- public static final ContentType TEXT_PLAIN = create("text/plain");
-
- public static final ContentType MULTIPART_MIXED = create("multipart/mixed");
+ public static final ContentType APPLICATION_JSON = new ContentType(APPLICATION, "json", null);
- public static final ContentType APPLICATION_XHTML_XML = create("application/xhtml+xml");
+ public static final ContentType APPLICATION_OCTET_STREAM = new ContentType(APPLICATION, "octet-stream", null);
- public static final ContentType APPLICATION_SVG_XML = create("application/svg+xml");
+ public static final ContentType APPLICATION_XHTML_XML = new ContentType(APPLICATION, "xhtml+xml", null);
+ public static final ContentType TEXT_HTML = new ContentType(TEXT, "html", null);
+ public static final ContentType TEXT_XML = new ContentType(TEXT, "xml", null);
+ public static final ContentType TEXT_PLAIN = new ContentType(TEXT, "plain", null);
- public static final ContentType APPLICATION_FORM_URLENCODED = create("application/x-www-form-urlencoded");
+ public static final ContentType APPLICATION_SVG_XML = new ContentType(APPLICATION, "svg+xml", null);
- public static final ContentType MULTIPART_FORM_DATA = create("multipart/form-data");
+ public static final ContentType APPLICATION_FORM_URLENCODED =
+ new ContentType(APPLICATION, "x-www-form-urlencoded", null);
- public static final ContentType TEXT_XML = create("text/xml");
+ public static final ContentType MULTIPART_MIXED = new ContentType(MULTIPART, "mixed", null);
- public static final ContentType TEXT_HTML = create("text/html");
+ public static final ContentType MULTIPART_FORM_DATA = new ContentType(MULTIPART, "form-data", null);
public static final String PARAMETER_CHARSET_UTF8 = "charset=utf-8";
private final String type;
-
private final String subtype;
-
private final Map<String, String> parameters;
/**
@@ -133,7 +129,7 @@ public final class ContentType {
}
/**
- * Creates a content type from format and key-value pairs for parameters
+ * Creates a content type from format and key-value pairs for parameters.
*
* @param format for example "application/json"
* @param parameters for example "a=b", "c=d"
@@ -151,7 +147,7 @@ public final class ContentType {
}
/**
- * Creates a content type from format and key-value pairs for parameters
+ * Creates a content type from an existing content type and additional key-value pairs for parameters.
*
* @param contentType for example "application/json"
* @param parameters for example "a=b", "c=d"
@@ -203,7 +199,7 @@ public final class ContentType {
}
}
- private static void parse(final String format, final List<String> typeSubtype, final Map<String, String> parameters) {
+ private static void parse(final String format, List<String> typeSubtype, Map<String, String> parameters) {
final String[] typesAndParameters = format.split(TypeUtil.PARAMETER_SEPARATOR, 2);
final String types = typesAndParameters[0];
final String params = (typesAndParameters.length > 1 ? typesAndParameters[1] : null);
@@ -253,50 +249,44 @@ public final class ContentType {
}
/**
- * {@link ContentType}s are equal
- * <ul>
- * <li>if <code>type</code>, <code>subtype</code> and all <code>parameters</code> have the same value.</li>
- * <li>if <code>type</code> and/or <code>subtype</code> is set to "*" (in such a case the <code>parameters</code> are
- * ignored).</li>
- * </ul>
- *
- * @return <code>true</code> if both instances are equal (see definition above), otherwise <code>false</code>.
+ * {@link ContentType}s are equal if <code>type</code>, <code>subtype</code>, and all <code>parameters</code>
+ * have the same value.
*/
@Override
public boolean equals(final Object obj) {
- // NULL validation is done in method 'isEqualWithoutParameters(obj)'
- final Boolean compatible = isEqualWithoutParameters(obj);
+ // basic checks
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
- if (compatible == null) {
- ContentType other = (ContentType) obj;
+ final ContentType other = (ContentType) obj;
+
+ // type/subtype checks
+ if (!isCompatible(other)) {
+ return false;
+ }
- // parameter checks
- if (parameters == null) {
- if (other.parameters != null) {
+ // parameter checks
+ if (parameters.size() == other.parameters.size()) {
+ final Iterator<Entry<String, String>> entries = parameters.entrySet().iterator();
+ final Iterator<Entry<String, String>> otherEntries = other.parameters.entrySet().iterator();
+ while (entries.hasNext()) {
+ final Entry<String, String> e = entries.next();
+ final Entry<String, String> oe = otherEntries.next();
+ if (!areEqual(e.getKey(), oe.getKey())) {
return false;
}
- } else if (parameters.size() == other.parameters.size()) {
- final Iterator<Entry<String, String>> entries = parameters.entrySet().iterator();
- final Iterator<Entry<String, String>> otherEntries = other.parameters.entrySet().iterator();
- while (entries.hasNext()) {
- final Entry<String, String> e = entries.next();
- final Entry<String, String> oe = otherEntries.next();
-
- if (!areEqual(e.getKey(), oe.getKey())) {
- return false;
- }
- if (!areEqual(e.getValue(), oe.getValue())) {
- return false;
- }
+ if (!areEqual(e.getValue(), oe.getValue())) {
+ return false;
}
- } else {
- return false;
}
- return true;
} else {
- // all tests run
- return compatible.booleanValue();
+ return false;
}
+ return true;
}
/**
@@ -304,71 +294,27 @@ public final class ContentType {
* if <code>type</code> and <code>subtype</code> have the same value.</p>
* <p>The set <code>parameters</code> are <b>always</b> ignored
* (for compare with parameters see {@link #equals(Object)}).</p>
- * @return <code>true</code> if both instances are equal (see definition above), otherwise <code>false</code>.
+ * @return <code>true</code> if both instances are compatible (see definition above), otherwise <code>false</code>.
*/
- public boolean isCompatible(final ContentType obj) {
- final Boolean compatible = isEqualWithoutParameters(obj);
- return compatible == null || compatible.booleanValue();
- }
-
- /**
- * Check equal without parameters. It is possible that no decision about <code>equal/none equal</code> can be
- * determined a <code>NULL</code> is returned.
- *
- * @param obj to checked object
- * @return <code>true</code> if both instances are equal (see definition above), otherwise <code>false</code> or
- * <code>NULL</code> if no decision about <code>equal/none equal</code> could be determined.
- */
- private Boolean isEqualWithoutParameters(final Object obj) {
- // basic checks
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
-
- ContentType other = (ContentType) obj;
-
- // subtype checks
- if (subtype == null) {
- if (other.subtype != null) {
- return false;
- }
- } else if (!subtype.equalsIgnoreCase(other.subtype)) {
- return false;
- }
-
- // type checks
- if (type == null) {
- if (other.type != null) {
- return false;
- }
- } else if (!type.equalsIgnoreCase(other.type)) {
- return false;
- }
-
- return null;
+ public boolean isCompatible(final ContentType other) {
+ return type.equalsIgnoreCase(other.type) && subtype.equalsIgnoreCase(other.subtype);
}
/**
- * Check whether both string are equal ignoring the case of the strings.
+ * Checks whether both strings are equal ignoring the case of the strings.
*
* @param first first string
* @param second second string
- * @return <code>true</code> if both strings are equal (by ignoring the case), otherwise <code>false</code> is
- * returned
+ * @return <code>true</code> if both strings are equal (ignoring the case), otherwise <code>false</code>
*/
private static boolean areEqual(final String first, final String second) {
return first == null && second == null || first.equalsIgnoreCase(second);
}
/**
- * Get {@link ContentType} as string as defined in RFC 7231 (http://www.ietf.org/rfc/rfc7231.txt, chapter 3.1.1.1:
- * Media Type)
+ * Gets {@link ContentType} as string as defined in
+ * <a href="http://www.ietf.org/rfc/rfc7231.txt">RFC 7231</a>, chapter 3.1.1.1:
+ * Media Type.
*
* @return string representation of <code>ContentType</code> object
*/
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/9e5b284a/lib/server-api/src/main/java/org/apache/olingo/server/api/ODataHttpHandler.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/ODataHttpHandler.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/ODataHttpHandler.java
index ad2e8ab..8e4f257 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/ODataHttpHandler.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/ODataHttpHandler.java
@@ -19,6 +19,7 @@
package org.apache.olingo.server.api;
import org.apache.olingo.server.api.processor.Processor;
+import org.apache.olingo.server.api.serializer.CustomContentTypeSupport;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -46,6 +47,13 @@ public interface ODataHttpHandler {
void register(Processor processor);
/**
+ * Registers a service implementation for modifying the standard list of supported
+ * content types.
+ * @see CustomContentTypeSupport
+ */
+ void register(CustomContentTypeSupport customContentTypeSupport);
+
+ /**
* Sets the split parameter which is used for service resolution.
* @param split the number of path segments reserved for service resolution; default is 0
*/
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/9e5b284a/lib/server-api/src/main/java/org/apache/olingo/server/api/ServiceMetadata.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/ServiceMetadata.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/ServiceMetadata.java
index f0ece5c..dc06b94 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/ServiceMetadata.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/ServiceMetadata.java
@@ -25,7 +25,7 @@ import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
import java.util.List;
/**
- *
+ * Metadata of an OData service like the Entity Data Model.
*/
public interface ServiceMetadata {
/**
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/9e5b284a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/CustomContentTypeSupport.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/CustomContentTypeSupport.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/CustomContentTypeSupport.java
deleted file mode 100644
index 4759919..0000000
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/CustomContentTypeSupport.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.
- */
-package org.apache.olingo.server.api.processor;
-
-import java.util.List;
-
-import org.apache.olingo.commons.api.format.ContentType;
-
-/**
- * A processor which supports custom content types can implement this interface.
- * The processor can also remove default content types if the default serializers
- * of Olingo are not used. By default this interface is not implemented and
- * a processor supports content types implemented by Olingo's default serializer
- * (e.g., <code>application/xml</code> for the metadata and
- * </code>application/json</code> for the service document).
- * Requesting a content type that is not supported results in an HTTP error
- * 406 (Not Acceptable).
- */
-public interface CustomContentTypeSupport {
-
- /**
- * Returns a list of supported content types.
- * @param defaultContentTypes content types supported by Olingo's serializer
- * @param processorClass the {@link Processor} of the current request
- * @return modified list of supported content types
- */
- public List<ContentType> modifySupportedContentTypes(
- List<ContentType> defaultContentTypes, Class<? extends Processor> processorClass);
-}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/9e5b284a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
index 3e02540..473c904 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
@@ -19,9 +19,7 @@
package org.apache.olingo.server.api.processor;
import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
import org.apache.olingo.commons.api.format.ContentType;
import org.apache.olingo.commons.api.format.ODataFormat;
import org.apache.olingo.commons.api.http.HttpHeader;
@@ -77,13 +75,9 @@ public class DefaultProcessor implements MetadataProcessor, ServiceDocumentProce
public void processException(ODataRequest request, ODataResponse response, ODataServerError serverError,
ContentType requestedContentType) {
try {
- if (ContentType.APPLICATION_XML.equals(requestedContentType)) {
- requestedContentType = ODataFormat.JSON.getContentType(ODataServiceVersion.V40);
- }
ODataSerializer serializer = odata.createSerializer(ODataFormat.fromContentType(requestedContentType));
- InputStream responseEntity = serializer.error(serverError);
+ response.setContent(serializer.error(serverError));
response.setStatusCode(serverError.getStatusCode());
- response.setContent(responseEntity);
response.setHeader(HttpHeader.CONTENT_TYPE, requestedContentType.toContentTypeString());
} catch (Exception e) {
// This should never happen but to be sure we have this catch here to prevent sending a stacktrace to a client.
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/9e5b284a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/ExceptionProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/ExceptionProcessor.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/ExceptionProcessor.java
index 170d6a4..0c062c0 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/ExceptionProcessor.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/ExceptionProcessor.java
@@ -26,7 +26,7 @@ import org.apache.olingo.server.api.ODataServerError;
/**
* Processor which is called if any exception occurs inside the library or another processor.
*/
-public interface ExceptionProcessor extends Processor{
+public interface ExceptionProcessor extends Processor {
/**
* Processes an exception. MUST NOT throw an exception!
@@ -36,5 +36,5 @@ public interface ExceptionProcessor extends Processor{
* @param requestedContentType the requested format for the error message
*/
public void processException(ODataRequest request, ODataResponse response, ODataServerError serverError,
- final ContentType requestedContentType);
+ ContentType requestedContentType);
}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/9e5b284a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/Processor.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/Processor.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/Processor.java
index bec28b4..2b5eec2 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/Processor.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/Processor.java
@@ -30,8 +30,9 @@ public interface Processor {
/**
* Initializes the processor for each HTTP request - response cycle.
- * @param odata - Olingo's root object, acting as a factory for various object types
- * @param edm - the EDM which needs to be created before the OData request handling takes place
+ * @param odata Olingo's root object, acting as a factory for various object types
+ * @param serviceMetadata metadata of the OData service like the EDM that have to be created
+ * before the OData request handling takes place
*/
- void init(OData odata, ServiceMetadata edm);
+ void init(OData odata, ServiceMetadata serviceMetadata);
}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/9e5b284a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/CustomContentTypeSupport.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/CustomContentTypeSupport.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/CustomContentTypeSupport.java
new file mode 100644
index 0000000..ca4f0cb
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/CustomContentTypeSupport.java
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+package org.apache.olingo.server.api.serializer;
+
+import java.util.List;
+
+import org.apache.olingo.commons.api.format.ContentType;
+
+/**
+ * <p>Processors that supports custom content types can implement this interface.</p>
+ * <p>The processor can also remove default content types if the default (de-)serializers
+ * of Olingo are not used. By default this interface is not implemented and
+ * a processor supports content types implemented by Olingo's default (de-)serializer
+ * (e.g., <code>application/xml</code> for the metadata and
+ * </code>application/json</code> for the service document).</p>
+ * <p>Requesting a content type that is not supported results in an HTTP error
+ * 406 (Not Acceptable); sending content of an unsupported type results in an
+ * HTTP error 415 (Unsupported Media Type).</p>
+ */
+public interface CustomContentTypeSupport {
+
+ /**
+ * Returns a list of supported content types.
+ * @param defaultContentTypes content types supported by Olingo's (de-)serializer
+ * @param type the current type of representation
+ * @return modified list of supported content types
+ */
+ public List<ContentType> modifySupportedContentTypes(
+ List<ContentType> defaultContentTypes, RepresentationType type);
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/9e5b284a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/RepresentationType.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/RepresentationType.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/RepresentationType.java
new file mode 100644
index 0000000..22efa28
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/RepresentationType.java
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+package org.apache.olingo.server.api.serializer;
+
+/**
+ * The different types of representations that form the body of either the
+ * OData request or the OData response, primarily used for content negotiation.
+ */
+public enum RepresentationType {
+ /** service document */ SERVICE,
+ /** metadata document */ METADATA,
+ /** batch request or response */ BATCH,
+ /** error document */ ERROR,
+ /** single entity */ ENTITY,
+ /** collection of entities (entity set) */ COLLECTION_ENTITY,
+ /** single primitive-type instance */ PRIMITIVE,
+ /** collection of primitive-type instances */ COLLECTION_PRIMITIVE,
+ /** single complex-type instance */ COMPLEX,
+ /** collection of complex-type instances */ COLLECTION_COMPLEX,
+ /** differences */ DIFFERENCES,
+ /** media entity */ MEDIA,
+ /** binary-type instance */ BINARY,
+ /** single reference */ REFERENCE,
+ /** collection of references */ COLLECTION_REFERENCE,
+ /** textual raw value of a primitive-type instance (except binary) */ VALUE,
+ /** count of instances */ COUNT,
+ /** parameters of an action */ ACTION_PARAMETERS
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/9e5b284a/lib/server-core/src/main/java/org/apache/olingo/server/core/ContentNegotiator.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ContentNegotiator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ContentNegotiator.java
index a4952ad..028b7b2 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ContentNegotiator.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ContentNegotiator.java
@@ -18,7 +18,7 @@
*/
package org.apache.olingo.server.core;
-import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
@@ -27,44 +27,52 @@ import org.apache.olingo.commons.api.format.ContentType;
import org.apache.olingo.commons.api.format.ODataFormat;
import org.apache.olingo.commons.api.http.HttpHeader;
import org.apache.olingo.server.api.ODataRequest;
-import org.apache.olingo.server.api.processor.CustomContentTypeSupport;
-import org.apache.olingo.server.api.processor.MetadataProcessor;
-import org.apache.olingo.server.api.processor.Processor;
+import org.apache.olingo.server.api.serializer.CustomContentTypeSupport;
+import org.apache.olingo.server.api.serializer.RepresentationType;
import org.apache.olingo.server.api.uri.queryoption.FormatOption;
public class ContentNegotiator {
private ContentNegotiator() {}
- private static List<ContentType> getDefaultSupportedContentTypes(final Class<? extends Processor> processorClass) {
- List<ContentType> defaults = new ArrayList<ContentType>();
-
- if (processorClass == MetadataProcessor.class) {
- defaults.add(ODataFormat.XML.getContentType(ODataServiceVersion.V40));
- } else {
- defaults.add(ODataFormat.JSON.getContentType(ODataServiceVersion.V40));
- defaults.add(ODataFormat.JSON_NO_METADATA.getContentType(ODataServiceVersion.V40));
+ private static List<ContentType> getDefaultSupportedContentTypes(final RepresentationType type) {
+ switch (type) {
+ case METADATA:
+ return Arrays.asList(ContentType.APPLICATION_XML);
+ case MEDIA:
+ case BINARY:
+ return Arrays.asList(ContentType.APPLICATION_OCTET_STREAM);
+ case VALUE:
+ case COUNT:
+ return Arrays.asList(ContentType.TEXT_PLAIN);
+ case BATCH:
+ return Arrays.asList(ContentType.MULTIPART_MIXED);
+ default:
+ return Arrays.asList(
+ ODataFormat.JSON.getContentType(ODataServiceVersion.V40),
+ ODataFormat.JSON_NO_METADATA.getContentType(ODataServiceVersion.V40));
}
-
- return defaults;
}
- private static List<ContentType> getSupportedContentTypes(final Processor processor,
- final Class<? extends Processor> processorClass) {
-
- List<ContentType> supportedContentTypes = getDefaultSupportedContentTypes(processorClass);
-
- if (processor instanceof CustomContentTypeSupport) {
- supportedContentTypes = ((CustomContentTypeSupport) processor)
- .modifySupportedContentTypes(supportedContentTypes, processorClass);
+ private static List<ContentType> getSupportedContentTypes(
+ final CustomContentTypeSupport customContentTypeSupport, final RepresentationType representationType)
+ throws ContentNegotiatorException {
+ final List<ContentType> defaultSupportedContentTypes = getDefaultSupportedContentTypes(representationType);
+ final List<ContentType> result = customContentTypeSupport == null ? defaultSupportedContentTypes :
+ customContentTypeSupport.modifySupportedContentTypes(defaultSupportedContentTypes, representationType);
+ if (result == null || result.isEmpty()) {
+ throw new ContentNegotiatorException("No content type has been specified as supported.",
+ ContentNegotiatorException.MessageKeys.NO_CONTENT_TYPE_SUPPORTED);
+ } else {
+ return result;
}
-
- return supportedContentTypes;
}
public static ContentType doContentNegotiation(final FormatOption formatOption, final ODataRequest request,
- final Processor processor, final Class<? extends Processor> processorClass) throws ContentNegotiatorException {
- final List<ContentType> supportedContentTypes = getSupportedContentTypes(processor, processorClass);
+ final CustomContentTypeSupport customContentTypeSupport, final RepresentationType representationType)
+ throws ContentNegotiatorException {
+ final List<ContentType> supportedContentTypes =
+ getSupportedContentTypes(customContentTypeSupport, representationType);
final String acceptHeaderValue = request.getHeader(HttpHeader.ACCEPT);
ContentType result = null;
@@ -98,9 +106,7 @@ public class ContentNegotiator {
ContentNegotiatorException.MessageKeys.UNSUPPORTED_CONTENT_TYPES, acceptedContentTypes.toString());
}
} else {
- final ContentType requestedContentType = processorClass == MetadataProcessor.class ?
- ODataFormat.XML.getContentType(ODataServiceVersion.V40) :
- ODataFormat.JSON.getContentType(ODataServiceVersion.V40);
+ final ContentType requestedContentType = getDefaultSupportedContentTypes(representationType).get(0);
result = getAcceptedType(AcceptType.fromContentType(requestedContentType), supportedContentTypes);
if (result == null) {
throw new ContentNegotiatorException(
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/9e5b284a/lib/server-core/src/main/java/org/apache/olingo/server/core/ContentNegotiatorException.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ContentNegotiatorException.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ContentNegotiatorException.java
index b30bd8a..efd597d 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ContentNegotiatorException.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ContentNegotiatorException.java
@@ -30,6 +30,7 @@ public class ContentNegotiatorException extends ODataTranslatedException {
UNSUPPORTED_CONTENT_TYPES,
/** parameter: content type */
UNSUPPORTED_CONTENT_TYPE,
+ NO_CONTENT_TYPE_SUPPORTED,
/** parameter: format string */
UNSUPPORTED_FORMAT_OPTION;
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/9e5b284a/lib/server-core/src/main/java/org/apache/olingo/server/core/DefaultRedirectProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/DefaultRedirectProcessor.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/DefaultRedirectProcessor.java
index 1dc3e9c..23e539b 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/DefaultRedirectProcessor.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/DefaultRedirectProcessor.java
@@ -28,7 +28,7 @@ import org.apache.olingo.server.api.ServiceMetadata;
public class DefaultRedirectProcessor implements RedirectProcessor {
@Override
- public void init(final OData odata, final ServiceMetadata edm) {}
+ public void init(final OData odata, final ServiceMetadata serviceMetadata) {}
@Override
public void redirect(final ODataRequest request, final ODataResponse response) {
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/9e5b284a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
index 4ac780d..a13ef6f 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
@@ -18,20 +18,22 @@
*/
package org.apache.olingo.server.core;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.LinkedList;
+import java.util.List;
+import org.apache.olingo.commons.api.edm.EdmPrimitiveType;
+import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
import org.apache.olingo.commons.api.format.ContentType;
import org.apache.olingo.commons.api.format.ODataFormat;
import org.apache.olingo.commons.api.http.HttpHeader;
import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory;
import org.apache.olingo.server.api.OData;
import org.apache.olingo.server.api.ODataApplicationException;
import org.apache.olingo.server.api.ODataRequest;
import org.apache.olingo.server.api.ODataResponse;
import org.apache.olingo.server.api.ODataServerError;
-import org.apache.olingo.server.api.ODataTranslatedException;
import org.apache.olingo.server.api.ServiceMetadata;
import org.apache.olingo.server.api.processor.DefaultProcessor;
import org.apache.olingo.server.api.processor.EntitySetProcessor;
@@ -41,11 +43,15 @@ import org.apache.olingo.server.api.processor.MetadataProcessor;
import org.apache.olingo.server.api.processor.Processor;
import org.apache.olingo.server.api.processor.PropertyProcessor;
import org.apache.olingo.server.api.processor.ServiceDocumentProcessor;
+import org.apache.olingo.server.api.serializer.CustomContentTypeSupport;
+import org.apache.olingo.server.api.serializer.RepresentationType;
import org.apache.olingo.server.api.serializer.SerializerException;
import org.apache.olingo.server.api.uri.UriInfo;
import org.apache.olingo.server.api.uri.UriResource;
+import org.apache.olingo.server.api.uri.UriResourceKind;
import org.apache.olingo.server.api.uri.UriResourceNavigation;
import org.apache.olingo.server.api.uri.UriResourcePartTyped;
+import org.apache.olingo.server.api.uri.UriResourceProperty;
import org.apache.olingo.server.core.uri.parser.Parser;
import org.apache.olingo.server.core.uri.parser.UriParserException;
import org.apache.olingo.server.core.uri.parser.UriParserSemanticException;
@@ -57,9 +63,10 @@ public class ODataHandler {
private final OData odata;
private final ServiceMetadata serviceMetadata;
- private final Map<Class<? extends Processor>, Processor> processors =
- new HashMap<Class<? extends Processor>, Processor>();
- private ContentType requestedContentType;
+ private List<Processor> processors = new LinkedList<Processor>();
+ private CustomContentTypeSupport customContentTypeSupport = null;
+
+ private UriInfo uriInfo;
public ODataHandler(final OData server, final ServiceMetadata serviceMetadata) {
odata = server;
@@ -77,31 +84,31 @@ public class ODataHandler {
} catch (final UriValidationException e) {
ODataServerError serverError = ODataExceptionHelper.createServerErrorObject(e, null);
- handleException(request, response, serverError, null);
+ handleException(request, response, serverError);
} catch (final UriParserSemanticException e) {
ODataServerError serverError = ODataExceptionHelper.createServerErrorObject(e, null);
- handleException(request, response, serverError, null);
+ handleException(request, response, serverError);
} catch (final UriParserSyntaxException e) {
ODataServerError serverError = ODataExceptionHelper.createServerErrorObject(e, null);
- handleException(request, response, serverError, null);
+ handleException(request, response, serverError);
} catch (final UriParserException e) {
ODataServerError serverError = ODataExceptionHelper.createServerErrorObject(e, null);
- handleException(request, response, serverError, null);
+ handleException(request, response, serverError);
} catch (ContentNegotiatorException e) {
ODataServerError serverError = ODataExceptionHelper.createServerErrorObject(e, null);
- handleException(request, response, serverError, null);
+ handleException(request, response, serverError);
} catch (SerializerException e) {
ODataServerError serverError = ODataExceptionHelper.createServerErrorObject(e, null);
- handleException(request, response, serverError, requestedContentType);
+ handleException(request, response, serverError);
} catch (ODataHandlerException e) {
ODataServerError serverError = ODataExceptionHelper.createServerErrorObject(e, null);
- handleException(request, response, serverError, requestedContentType);
+ handleException(request, response, serverError);
} catch (ODataApplicationException e) {
ODataServerError serverError = ODataExceptionHelper.createServerErrorObject(e);
- handleException(request, response, serverError, requestedContentType);
+ handleException(request, response, serverError);
} catch (Exception e) {
ODataServerError serverError = ODataExceptionHelper.createServerErrorObject(e);
- handleException(request, response, serverError, requestedContentType);
+ handleException(request, response, serverError);
}
return response;
}
@@ -111,48 +118,45 @@ public class ODataHandler {
ODataApplicationException, SerializerException {
validateODataVersion(request, response);
- Parser parser = new Parser();
- final UriInfo uriInfo = parser.parseUri(
- request.getRawODataPath(), request.getRawQueryPath(),
- null, serviceMetadata.getEdm());
+ uriInfo = new Parser().parseUri(request.getRawODataPath(), request.getRawQueryPath(), null,
+ serviceMetadata.getEdm());
- UriValidator validator = new UriValidator();
- validator.validate(uriInfo, request.getMethod());
+ final HttpMethod method = request.getMethod();
+ new UriValidator().validate(uriInfo, method);
switch (uriInfo.getKind()) {
case metadata:
- if (request.getMethod().equals(HttpMethod.GET)) {
+ if (method.equals(HttpMethod.GET)) {
MetadataProcessor mp = selectProcessor(MetadataProcessor.class);
- requestedContentType =
- ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request, mp, MetadataProcessor.class);
+ ContentType requestedContentType = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request,
+ customContentTypeSupport, RepresentationType.METADATA);
mp.readMetadata(request, response, uriInfo, requestedContentType);
} else {
- throw new ODataHandlerException("HttpMethod " + request.getMethod() + " not allowed for metadata document",
- ODataHandlerException.MessageKeys.HTTP_METHOD_NOT_ALLOWED, request.getMethod().toString());
+ throw new ODataHandlerException("HttpMethod " + method + " not allowed for metadata document",
+ ODataHandlerException.MessageKeys.HTTP_METHOD_NOT_ALLOWED, method.toString());
}
break;
case service:
- if (request.getMethod().equals(HttpMethod.GET)) {
+ if (method.equals(HttpMethod.GET)) {
if ("".equals(request.getRawODataPath())) {
RedirectProcessor rdp = selectProcessor(RedirectProcessor.class);
rdp.redirect(request, response);
} else {
ServiceDocumentProcessor sdp = selectProcessor(ServiceDocumentProcessor.class);
- requestedContentType =
- ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request, sdp,
- ServiceDocumentProcessor.class);
+ ContentType requestedContentType = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request,
+ customContentTypeSupport, RepresentationType.SERVICE);
sdp.readServiceDocument(request, response, uriInfo, requestedContentType);
}
} else {
- throw new ODataHandlerException("HttpMethod " + request.getMethod() + " not allowed for service document",
- ODataHandlerException.MessageKeys.HTTP_METHOD_NOT_ALLOWED, request.getMethod().toString());
+ throw new ODataHandlerException("HttpMethod " + method + " not allowed for service document",
+ ODataHandlerException.MessageKeys.HTTP_METHOD_NOT_ALLOWED, method.toString());
}
break;
case resource:
- handleResourceDispatching(request, response, uriInfo);
+ handleResourceDispatching(request, response);
break;
default:
throw new ODataHandlerException("not implemented",
@@ -160,25 +164,28 @@ public class ODataHandler {
}
}
- public void handleException(ODataRequest request, ODataResponse response, ODataServerError serverError,
- ContentType requestedContentType) {
+ public void handleException(ODataRequest request, ODataResponse response, ODataServerError serverError) {
ExceptionProcessor exceptionProcessor;
try {
exceptionProcessor = selectProcessor(ExceptionProcessor.class);
- } catch (ODataTranslatedException e) {
- // This cannot happen since there is always an ExceptionProcessor registered
+ } catch (ODataHandlerException e) {
+ // This cannot happen since there is always an ExceptionProcessor registered.
exceptionProcessor = new DefaultProcessor();
}
- if (requestedContentType == null) {
+ ContentType requestedContentType;
+ try {
+ requestedContentType = ContentNegotiator.doContentNegotiation(
+ uriInfo == null ? null : uriInfo.getFormatOption(), request, customContentTypeSupport,
+ RepresentationType.ERROR);
+ } catch (final ContentNegotiatorException e) {
requestedContentType = ODataFormat.JSON.getContentType(ODataServiceVersion.V40);
}
exceptionProcessor.processException(request, response, serverError, requestedContentType);
}
- private void handleResourceDispatching(final ODataRequest request, final ODataResponse response,
- final UriInfo uriInfo) throws ODataHandlerException, ContentNegotiatorException, ODataApplicationException,
- SerializerException {
- int lastPathSegmentIndex = uriInfo.getUriResourceParts().size() - 1;
+ private void handleResourceDispatching(final ODataRequest request, final ODataResponse response)
+ throws ODataHandlerException, ContentNegotiatorException, ODataApplicationException, SerializerException {
+ final int lastPathSegmentIndex = uriInfo.getUriResourceParts().size() - 1;
UriResource lastPathSegment = uriInfo.getUriResourceParts().get(lastPathSegmentIndex);
switch (lastPathSegment.getKind()) {
@@ -187,9 +194,8 @@ public class ODataHandler {
if (request.getMethod().equals(HttpMethod.GET)) {
EntitySetProcessor cp = selectProcessor(EntitySetProcessor.class);
- requestedContentType =
- ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request, cp,
- EntitySetProcessor.class);
+ ContentType requestedContentType = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request,
+ customContentTypeSupport, RepresentationType.COLLECTION_ENTITY);
cp.readEntitySet(request, response, uriInfo, requestedContentType);
} else {
@@ -200,8 +206,8 @@ public class ODataHandler {
if (request.getMethod().equals(HttpMethod.GET)) {
EntityProcessor ep = selectProcessor(EntityProcessor.class);
- requestedContentType =
- ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request, ep, EntityProcessor.class);
+ ContentType requestedContentType = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request,
+ customContentTypeSupport, RepresentationType.ENTITY);
ep.readEntity(request, response, uriInfo, requestedContentType);
} else {
@@ -215,9 +221,8 @@ public class ODataHandler {
if (request.getMethod().equals(HttpMethod.GET)) {
EntitySetProcessor cp = selectProcessor(EntitySetProcessor.class);
- requestedContentType =
- ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request, cp,
- EntitySetProcessor.class);
+ ContentType requestedContentType = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request,
+ customContentTypeSupport, RepresentationType.COLLECTION_ENTITY);
cp.readEntitySet(request, response, uriInfo, requestedContentType);
} else {
@@ -228,8 +233,8 @@ public class ODataHandler {
if (request.getMethod().equals(HttpMethod.GET)) {
EntityProcessor ep = selectProcessor(EntityProcessor.class);
- requestedContentType =
- ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request, ep, EntityProcessor.class);
+ ContentType requestedContentType = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request,
+ customContentTypeSupport, RepresentationType.ENTITY);
ep.readEntity(request, response, uriInfo, requestedContentType);
} else {
@@ -252,8 +257,14 @@ public class ODataHandler {
if (request.getMethod().equals(HttpMethod.GET)) {
PropertyProcessor ep = selectProcessor(PropertyProcessor.class);
- requestedContentType =
- ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request, ep, PropertyProcessor.class);
+ final UriResourceProperty propertyResource = (UriResourceProperty) lastPathSegment;
+ final boolean isCollection = propertyResource.isCollection();
+ final boolean isComplex = propertyResource.getKind() == UriResourceKind.complexProperty;
+ final RepresentationType representationType =
+ isComplex ? isCollection ? RepresentationType.COLLECTION_COMPLEX : RepresentationType.COMPLEX :
+ isCollection ? RepresentationType.COLLECTION_PRIMITIVE : RepresentationType.PRIMITIVE;
+ ContentType requestedContentType = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request,
+ customContentTypeSupport, representationType);
ep.readProperty(request, response, uriInfo, requestedContentType);
} else {
@@ -264,8 +275,17 @@ public class ODataHandler {
case value:
if (request.getMethod().equals(HttpMethod.GET)) {
PropertyProcessor ep = selectProcessor(PropertyProcessor.class);
- requestedContentType =
- ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request, ep, PropertyProcessor.class);
+
+ final UriResourceProperty propertyResource =
+ (UriResourceProperty) uriInfo.getUriResourceParts().get(lastPathSegmentIndex - 1);
+ final RepresentationType representationType =
+ (EdmPrimitiveType) propertyResource.getType() ==
+ EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Binary) ?
+ RepresentationType.BINARY :
+ RepresentationType.VALUE;
+ ContentType requestedContentType = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request,
+ customContentTypeSupport, representationType);
+
ep.readPropertyValue(request, response, uriInfo, requestedContentType);
} else {
throw new ODataHandlerException("not implemented",
@@ -280,7 +300,7 @@ public class ODataHandler {
private void validateODataVersion(final ODataRequest request, final ODataResponse response)
throws ODataHandlerException {
- String maxVersion = request.getHeader(HttpHeader.ODATA_MAX_VERSION);
+ final String maxVersion = request.getHeader(HttpHeader.ODATA_MAX_VERSION);
response.setHeader(HttpHeader.ODATA_VERSION, ODataServiceVersion.V40.toString());
if (maxVersion != null) {
@@ -292,26 +312,21 @@ public class ODataHandler {
}
private <T extends Processor> T selectProcessor(final Class<T> cls) throws ODataHandlerException {
- @SuppressWarnings("unchecked")
- T p = (T) processors.get(cls);
-
- if (p == null) {
- throw new ODataHandlerException("Processor: " + cls.getName() + " not registered.",
- ODataHandlerException.MessageKeys.PROCESSOR_NOT_IMPLEMENTED, cls.getName());
+ for (final Processor processor : processors) {
+ if (cls.isAssignableFrom(processor.getClass())) {
+ processor.init(odata, serviceMetadata);
+ return cls.cast(processor);
+ }
}
-
- return p;
+ throw new ODataHandlerException("Processor: " + cls.getSimpleName() + " not registered.",
+ ODataHandlerException.MessageKeys.PROCESSOR_NOT_IMPLEMENTED, cls.getSimpleName());
}
public void register(final Processor processor) {
- processor.init(odata, serviceMetadata);
+ processors.add(0, processor);
+ }
- for (Class<?> cls : processor.getClass().getInterfaces()) {
- if (Processor.class.isAssignableFrom(cls) && cls != Processor.class) {
- @SuppressWarnings("unchecked")
- Class<? extends Processor> procClass = (Class<? extends Processor>) cls;
- processors.put(procClass, processor);
- }
- }
+ public void register(final CustomContentTypeSupport customContentTypeSupport) {
+ this.customContentTypeSupport = customContentTypeSupport;
}
}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/9e5b284a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHttpHandlerImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHttpHandlerImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHttpHandlerImpl.java
index bb31e26..b06c3c8 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHttpHandlerImpl.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHttpHandlerImpl.java
@@ -40,6 +40,7 @@ import org.apache.olingo.server.api.ODataServerError;
import org.apache.olingo.server.api.ODataTranslatedException;
import org.apache.olingo.server.api.ServiceMetadata;
import org.apache.olingo.server.api.processor.Processor;
+import org.apache.olingo.server.api.serializer.CustomContentTypeSupport;
import org.apache.olingo.server.api.serializer.SerializerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -85,7 +86,7 @@ public class ODataHttpHandlerImpl implements ODataHttpHandler {
} else {
serverError = ODataExceptionHelper.createServerErrorObject(e);
}
- handler.handleException(odRequest, resp, serverError, null);
+ handler.handleException(odRequest, resp, serverError);
return resp;
}
@@ -230,4 +231,9 @@ public class ODataHttpHandlerImpl implements ODataHttpHandler {
public void register(final Processor processor) {
handler.register(processor);
}
+
+ @Override
+ public void register(final CustomContentTypeSupport customContentTypeSupport) {
+ handler.register(customContentTypeSupport);
+ }
}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/9e5b284a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParseTreeVisitor.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParseTreeVisitor.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParseTreeVisitor.java
index 63d8fb9..f97b7e0 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParseTreeVisitor.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParseTreeVisitor.java
@@ -651,7 +651,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> {
}
private String getName(final EdmType type) {
- return type.getNamespace() + "." + type.getName();
+ return type.getFullQualifiedName().getFullQualifiedNameAsString();
}
@Override
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/9e5b284a/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties b/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties
index 87d08f8..07d9250 100644
--- a/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties
+++ b/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties
@@ -79,6 +79,7 @@ UriValidationException.UNALLOWED_KIND_BEFORE_COUNT=The kind '%1$s' is not allowe
ContentNegotiatorException.WRONG_CHARSET_IN_HEADER=The HTTP header '%1$s' with value '%2$s' contains an invalid character-set specification.
ContentNegotiatorException.UNSUPPORTED_CONTENT_TYPES=The content-type range '%1$s' is not supported.
ContentNegotiatorException.UNSUPPORTED_CONTENT_TYPE=The content type '%1$s' is not supported.
+ContentNegotiatorException.NO_CONTENT_TYPE_SUPPORTED=No content type has been specified as supported.
ContentNegotiatorException.UNSUPPORTED_FORMAT_OPTION=The $format option '%1$s' is not supported.
SerializerException.NOT_IMPLEMENTED=The requested serialization method has not been implemented yet.
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/9e5b284a/lib/server-core/src/test/java/org/apache/olingo/server/core/ContentNegotiatorTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/ContentNegotiatorTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/ContentNegotiatorTest.java
index 0a958e2..46111cc 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/ContentNegotiatorTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/ContentNegotiatorTest.java
@@ -21,6 +21,8 @@ package org.apache.olingo.server.core;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyListOf;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -29,18 +31,11 @@ import java.util.Arrays;
import java.util.List;
import org.apache.olingo.commons.api.format.ContentType;
+import org.apache.olingo.commons.api.http.HttpContentType;
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.ODataRequest;
-import org.apache.olingo.server.api.ODataResponse;
-import org.apache.olingo.server.api.ServiceMetadata;
-import org.apache.olingo.server.api.processor.CustomContentTypeSupport;
-import org.apache.olingo.server.api.processor.EntitySetProcessor;
-import org.apache.olingo.server.api.processor.MetadataProcessor;
-import org.apache.olingo.server.api.processor.Processor;
-import org.apache.olingo.server.api.processor.ServiceDocumentProcessor;
-import org.apache.olingo.server.api.uri.UriInfo;
+import org.apache.olingo.server.api.serializer.CustomContentTypeSupport;
+import org.apache.olingo.server.api.serializer.RepresentationType;
import org.apache.olingo.server.api.uri.queryoption.FormatOption;
import org.junit.Test;
@@ -51,15 +46,15 @@ public class ContentNegotiatorTest {
static final private String ACCEPT_CASE_FULL = "application/json;odata.metadata=full";
static final private String ACCEPT_CASE_NONE = "application/json;odata.metadata=none";
static final private String ACCEPT_CASE_JSONQ = "application/json;q=0.2";
- static final private String ACCEPT_CASE_XML = "application/xml";
- static final private String ACCEPT_CASE_WILDCARD1 = "*/*";
+ static final private String ACCEPT_CASE_XML = HttpContentType.APPLICATION_XML;
+ static final private String ACCEPT_CASE_WILDCARD1 = HttpContentType.WILDCARD;
static final private String ACCEPT_CASE_WILDCARD2 = "application/*";
//@formatter:off (Eclipse formatter)
//CHECKSTYLE:OFF (Maven checkstyle)
String[][] casesServiceDocument = {
- /* expected $format accept additional content types */
+ /* expected $format accept modified content types */
{ ACCEPT_CASE_MIN, null, null, null },
{ ACCEPT_CASE_MIN, "json", null, null },
{ ACCEPT_CASE_MIN, "json", ACCEPT_CASE_JSONQ, null },
@@ -68,8 +63,8 @@ public class ContentNegotiatorTest {
{ ACCEPT_CASE_MIN, null, ACCEPT_CASE_JSONQ, null },
{ ACCEPT_CASE_MIN, null, ACCEPT_CASE_WILDCARD1, null },
{ ACCEPT_CASE_MIN, null, ACCEPT_CASE_WILDCARD2, null },
+ { ACCEPT_CASE_MIN, null, null, ACCEPT_CASE_MIN },
{ "a/a", "a/a", null, "a/a,b/b" },
- { "a/a", " a/a ", null, " a/a , b/b " },
{ "a/a;x=y", "a/a", ACCEPT_CASE_WILDCARD1, "a/a;x=y" },
{ "a/a;v=w;x=y", null, "a/a;x=y", "a/a;b=c,a/a;v=w;x=y" },
{ "a/a;v=w;x=y", "a/a;x=y", null, "a/a;b=c,a/a;v=w;x=y" },
@@ -79,7 +74,7 @@ public class ContentNegotiatorTest {
};
String[][] casesMetadata = {
- /* expected $format accept additional content types */
+ /* expected $format accept modified content types */
{ ACCEPT_CASE_XML, null, null, null },
{ ACCEPT_CASE_XML, "xml", null, null },
{ ACCEPT_CASE_XML, "xml", ACCEPT_CASE_XML, null },
@@ -88,12 +83,11 @@ public class ContentNegotiatorTest {
{ ACCEPT_CASE_XML, null, ACCEPT_CASE_WILDCARD1, null },
{ ACCEPT_CASE_XML, null, ACCEPT_CASE_WILDCARD2, null },
{ "a/a", "a/a", null, "a/a,b/b" },
- { "a/a", " a/a ", null, " a/a , b/b " },
{ "a/a;x=y", "a/a", ACCEPT_CASE_WILDCARD1, "a/a;x=y" }
};
String[][] casesFail = {
- /* expected $format accept additional content types */
+ /* expected $format accept modified content types */
{ null, "xxx/yyy", null, null },
{ null, "a/a", null, "b/b" },
{ null, "a/a;x=y", null, "a/a;v=w" },
@@ -101,71 +95,77 @@ public class ContentNegotiatorTest {
{ null, "atom", null, null }, // not yet supported
{ null, null, ACCEPT_CASE_FULL, null }, // not yet supported
{ null, "a/b;charset=ISO-8859-1", null, "a/b" },
- { null, null, "a/b;charset=ISO-8859-1", "a/b" }
+ { null, null, "a/b;charset=ISO-8859-1", "a/b" },
+ { null, null, null, "text/plain" }
};
//CHECKSTYLE:ON
//@formatter:on
@Test
- public void testServiceDocumentSingleCase() throws Exception {
+ public void serviceDocumentSingleCase() throws Exception {
testContentNegotiation(
new String[] { ACCEPT_CASE_MIN_UTF8, null, ACCEPT_CASE_MIN_UTF8, null },
- ServiceDocumentProcessor.class);
+ RepresentationType.SERVICE);
}
@Test
- public void testServiceDocument() throws Exception {
+ public void serviceDocument() throws Exception {
for (String[] useCase : casesServiceDocument) {
- testContentNegotiation(useCase, ServiceDocumentProcessor.class);
+ testContentNegotiation(useCase, RepresentationType.SERVICE);
}
}
@Test
- public void testMetadataSingleCase() throws Exception {
- testContentNegotiation(new String[] { ACCEPT_CASE_XML, null, null, null }, MetadataProcessor.class);
+ public void metadataSingleCase() throws Exception {
+ testContentNegotiation(new String[] { ACCEPT_CASE_XML, null, null, null }, RepresentationType.METADATA);
}
@Test(expected = ContentNegotiatorException.class)
- public void testMetadataJsonFail() throws Exception {
- testContentNegotiation(new String[] { null, "json", null, null }, MetadataProcessor.class);
+ public void metadataJsonFail() throws Exception {
+ testContentNegotiation(new String[] { null, "json", null, null }, RepresentationType.METADATA);
}
@Test
- public void testMetadata() throws Exception {
+ public void metadata() throws Exception {
for (String[] useCase : casesMetadata) {
- testContentNegotiation(useCase, MetadataProcessor.class);
+ testContentNegotiation(useCase, RepresentationType.METADATA);
}
}
@Test
- public void testEntityCollectionFail() throws Exception {
+ public void entityCollectionFail() throws Exception {
for (String[] useCase : casesFail) {
try {
- testContentNegotiation(useCase, EntitySetProcessor.class);
+ testContentNegotiation(useCase, RepresentationType.COLLECTION_ENTITY);
fail("Exception expected for '" + useCase[1] + '|' + useCase[2] + '|' + useCase[3] + "'!");
} catch (final ContentNegotiatorException e) {}
}
}
- private void testContentNegotiation(final String[] useCase, final Class<? extends Processor> processorClass)
+ private void testContentNegotiation(final String[] useCase, final RepresentationType representationType)
throws ContentNegotiatorException {
- ODataRequest request = new ODataRequest();
- request.setMethod(HttpMethod.GET);
- request.setRawODataPath("/" + (useCase[1] == null ? "" : "?$format=" + useCase[1]));
-
- ProcessorStub p = new ProcessorStub(createCustomContentTypes(useCase[3]));
- FormatOption fo = null;
+ FormatOption formatOption = null;
if (useCase[1] != null) {
- fo = mock(FormatOption.class);
- when(fo.getFormat()).thenReturn(useCase[1].trim());
+ formatOption = mock(FormatOption.class);
+ when(formatOption.getFormat()).thenReturn(useCase[1]);
}
+ ODataRequest request = new ODataRequest();
if (useCase[2] != null) {
request.addHeader(HttpHeader.ACCEPT, Arrays.asList(useCase[2]));
}
- final ContentType requestedContentType = ContentNegotiator.doContentNegotiation(fo, request, p, processorClass);
+ CustomContentTypeSupport customContentTypeSupport = null;
+ if (useCase[3] != null) {
+ customContentTypeSupport = mock(CustomContentTypeSupport.class);
+ when(customContentTypeSupport.modifySupportedContentTypes(
+ anyListOf(ContentType.class), any(RepresentationType.class)))
+ .thenReturn(createCustomContentTypes(useCase[3]));
+ }
+
+ final ContentType requestedContentType = ContentNegotiator.doContentNegotiation(
+ formatOption, request, customContentTypeSupport, representationType);
assertNotNull(requestedContentType);
if (useCase[0] != null) {
@@ -174,66 +174,13 @@ public class ContentNegotiatorTest {
}
private List<ContentType> createCustomContentTypes(final String contentTypeString) {
-
- if (contentTypeString == null) {
- return null;
- }
-
- String[] contentTypes = contentTypeString.split(",");
+ final String[] contentTypes = contentTypeString.split(",");
List<ContentType> types = new ArrayList<ContentType>();
for (int i = 0; i < contentTypes.length; i++) {
- types.add(ContentType.create(contentTypes[i].trim()));
+ types.add(ContentType.create(contentTypes[i]));
}
return types;
}
-
- private class ProcessorStub implements ServiceDocumentProcessor, MetadataProcessor,
- EntitySetProcessor, CustomContentTypeSupport {
-
- List<ContentType> customTypes;
-
- ProcessorStub(final List<ContentType> types) {
- customTypes = types;
- }
-
- @Override
- public void init(final OData odata, final ServiceMetadata edm) {}
-
- @Override
- public List<ContentType> modifySupportedContentTypes(final List<ContentType> supportedContentTypes,
- final Class<? extends Processor> processorClass) {
- if (customTypes == null) {
- return supportedContentTypes;
- } else {
- List<ContentType> modifiedTypes = new ArrayList<ContentType>(supportedContentTypes);
- modifiedTypes.addAll(customTypes);
- return modifiedTypes;
- }
- }
-
- @Override
- public void readServiceDocument(final ODataRequest request, final ODataResponse response, final UriInfo uriInfo,
- final ContentType format) {
- response.setHeader(HttpHeader.CONTENT_TYPE, format.toContentTypeString());
- }
-
- @Override
- public void readEntitySet(final ODataRequest request, final ODataResponse response, final UriInfo uriInfo,
- final ContentType requestedContentType) {
- response.setHeader(HttpHeader.CONTENT_TYPE, requestedContentType.toContentTypeString());
- }
-
- @Override
- public void readMetadata(final ODataRequest request, final ODataResponse response, final UriInfo uriInfo,
- final ContentType requestedContentType) {
- response.setHeader(HttpHeader.CONTENT_TYPE, requestedContentType.toContentTypeString());
- }
-
- @Override
- public void countEntitySet(ODataRequest request, ODataResponse response, UriInfo uriInfo) {
- response.setHeader(HttpHeader.CONTENT_TYPE, ContentType.TEXT_PLAIN.toContentTypeString());
- }
- }
}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/9e5b284a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
index ca87c6b..f05002e 100644
--- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
+++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
@@ -33,12 +33,14 @@ import org.apache.olingo.commons.api.data.Property;
import org.apache.olingo.commons.api.edm.EdmEntitySet;
import org.apache.olingo.commons.api.edm.EdmPrimitiveType;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException;
+import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
import org.apache.olingo.commons.api.edm.EdmProperty;
import org.apache.olingo.commons.api.format.ContentType;
import org.apache.olingo.commons.api.format.ODataFormat;
import org.apache.olingo.commons.api.http.HttpContentType;
import org.apache.olingo.commons.api.http.HttpHeader;
import org.apache.olingo.commons.api.http.HttpStatusCode;
+import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory;
import org.apache.olingo.server.api.OData;
import org.apache.olingo.server.api.ODataApplicationException;
import org.apache.olingo.server.api.ODataRequest;
@@ -74,7 +76,7 @@ public class TechnicalProcessor implements EntitySetProcessor, EntityProcessor,
}
@Override
- public void init(final OData odata, final ServiceMetadata edm) {
+ public void init(final OData odata, final ServiceMetadata serviceMetadata) {
this.odata = odata;
}
@@ -280,20 +282,24 @@ public class TechnicalProcessor implements EntitySetProcessor, EntityProcessor,
response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode());
} else {
final EdmPrimitiveType type = (EdmPrimitiveType) edmProperty.getType();
- try {
- final String value = type.valueToString(property.getValue(),
- edmProperty.isNullable(), edmProperty.getMaxLength(),
- edmProperty.getPrecision(), edmProperty.getScale(), edmProperty.isUnicode());
- response.setContent(new ByteArrayInputStream(value.getBytes("UTF-8")));
- } catch (final EdmPrimitiveTypeException e) {
- throw new ODataApplicationException("Error in value formatting.",
- HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ROOT, e);
- } catch (final UnsupportedEncodingException e) {
- throw new ODataApplicationException("Encoding exception.",
- HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ROOT, e);
+ if (type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Binary)) {
+ response.setContent(new ByteArrayInputStream((byte[]) property.getValue()));
+ } else {
+ try {
+ final String value = type.valueToString(property.getValue(),
+ edmProperty.isNullable(), edmProperty.getMaxLength(),
+ edmProperty.getPrecision(), edmProperty.getScale(), edmProperty.isUnicode());
+ response.setContent(new ByteArrayInputStream(value.getBytes("UTF-8")));
+ } catch (final EdmPrimitiveTypeException e) {
+ throw new ODataApplicationException("Error in value formatting.",
+ HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ROOT, e);
+ } catch (final UnsupportedEncodingException e) {
+ throw new ODataApplicationException("Encoding exception.",
+ HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ROOT, e);
+ }
}
+ response.setHeader(HttpHeader.CONTENT_TYPE, contentType.toContentTypeString());
response.setStatusCode(HttpStatusCode.OK.getStatusCode());
- response.setHeader(HttpHeader.CONTENT_TYPE, ContentType.TEXT_PLAIN.toContentTypeString());
}
}
}