Repository: olingo-odata4 Updated Branches: refs/heads/master 5a9995570 -> 9e5b284a5
[OLINGO-444] improved server content negotiation Change-Id: I30f53dc05eec9ff669a029f1382a37dd220abe94 Signed-off-by: Michael Bolz <[email protected]> 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 <[email protected]> Authored: Wed Oct 22 15:23:28 2014 +0200 Committer: Michael Bolz <[email protected]> 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()); } } }
