From acc12ff742cca555dd2732f78582e5cac3056a02 Mon Sep 17 00:00:00 2001 From: Stephan Klevenz Date: Fri, 27 Jun 2014 17:33:20 +0200 Subject: [PATCH] [OLINGO-328] refactor content negotiation --- lib/server-api/pom.xml | 5 + .../olingo/server/api/ODataHttpHandler.java | 2 - .../olingo/server/api/ODataRequest.java | 2 +- ...pes.java => CustomContentTypeSupport.java} | 7 +- .../api/processor/DefaultProcessor.java | 4 +- .../processor/FormatContentTypeMapping.java | 53 +++ .../api/processor/MetadataProcessor.java | 2 +- .../server/api/processor/Processor.java | 2 - .../olingo/server/api/ODataRequestTest.java | 0 .../olingo/server/core/ContentNegotiator.java | 132 +++++++ .../olingo/server/core/ODataHandler.java | 63 +++- .../server/core/ODataHttpHandlerImpl.java | 2 +- .../server/core/ContentNegotiatorTest.java | 327 ++++++++++++++++++ .../server/core/ODataHttpHandlerImplTest.java | 2 + .../server/core/ContentNegotiationTest.java | 201 ----------- 15 files changed, 576 insertions(+), 228 deletions(-) rename lib/server-api/src/main/java/org/apache/olingo/server/api/processor/{SupportCustomContentTypes.java => CustomContentTypeSupport.java} (80%) create mode 100644 lib/server-api/src/main/java/org/apache/olingo/server/api/processor/FormatContentTypeMapping.java rename lib/{server-test => server-api}/src/test/java/org/apache/olingo/server/api/ODataRequestTest.java (100%) create mode 100644 lib/server-core/src/main/java/org/apache/olingo/server/core/ContentNegotiator.java create mode 100644 lib/server-core/src/test/java/org/apache/olingo/server/core/ContentNegotiatorTest.java delete mode 100644 lib/server-test/src/test/java/org/apache/olingo/server/core/ContentNegotiationTest.java diff --git a/lib/server-api/pom.xml b/lib/server-api/pom.xml index 3d8fa8788..6af6456be 100644 --- a/lib/server-api/pom.xml +++ b/lib/server-api/pom.xml @@ -46,6 +46,11 @@ 2.5 provided + + + junit + junit + 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 e9d9caa7d..8372f8036 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 @@ -18,8 +18,6 @@ */ package org.apache.olingo.server.api; -import java.util.List; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/ODataRequest.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/ODataRequest.java index 5be8b96d7..45cf503f7 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/ODataRequest.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/ODataRequest.java @@ -54,7 +54,7 @@ public class ODataRequest { String key = name.toUpperCase(); if (headers.containsKey(key)) { List oldValues = headers.get(key); - + List newValues = new ArrayList(); newValues.addAll(oldValues); newValues.addAll(values); diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/SupportCustomContentTypes.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/CustomContentTypeSupport.java similarity index 80% rename from lib/server-api/src/main/java/org/apache/olingo/server/api/processor/SupportCustomContentTypes.java rename to lib/server-api/src/main/java/org/apache/olingo/server/api/processor/CustomContentTypeSupport.java index 0a032e102..75a57b101 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/SupportCustomContentTypes.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/CustomContentTypeSupport.java @@ -20,8 +20,9 @@ package org.apache.olingo.server.api.processor; import java.util.List; -public interface SupportCustomContentTypes { +public interface CustomContentTypeSupport { + + public List modifySupportedContentTypes( + List supportedContentTypes, Class processorClass); - public List getSupportedContentTypes(Class processorClass); - } 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 4d1a94d25..3ce2570b7 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,8 +19,6 @@ package org.apache.olingo.server.api.processor; import java.io.InputStream; -import java.util.Collections; -import java.util.List; import org.apache.olingo.commons.api.edm.Edm; import org.apache.olingo.commons.api.format.ODataFormat; @@ -33,7 +31,7 @@ import org.apache.olingo.server.api.serializer.ODataSerializer; import org.apache.olingo.server.api.uri.UriInfo; public class DefaultProcessor implements MetadataProcessor, ServiceDocumentProcessor { - + private OData odata; private Edm edm; diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/FormatContentTypeMapping.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/FormatContentTypeMapping.java new file mode 100644 index 000000000..2fe69b60c --- /dev/null +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/FormatContentTypeMapping.java @@ -0,0 +1,53 @@ +/* + * 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; + +public class FormatContentTypeMapping { + + private String formatAlias; + private String contentType; + + public FormatContentTypeMapping(String formatAlias, String contentType) { + super(); + this.formatAlias = formatAlias; + this.contentType = contentType; + } + + public String getFormatAlias() { + return formatAlias; + } + + public String getContentType() { + return contentType; + } + + public void setFormatAlias(String formatAlias) { + this.formatAlias = formatAlias; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + @Override + public String toString() { + return "('" + formatAlias + "', '" + contentType + "')"; + } + +} \ No newline at end of file diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/MetadataProcessor.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/MetadataProcessor.java index 64d5c1667..89f8e5303 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/MetadataProcessor.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/MetadataProcessor.java @@ -22,7 +22,7 @@ import org.apache.olingo.server.api.ODataRequest; import org.apache.olingo.server.api.ODataResponse; import org.apache.olingo.server.api.uri.UriInfo; -public interface MetadataProcessor extends Processor{ +public interface MetadataProcessor extends Processor { void readMetadata(ODataRequest request, ODataResponse response, UriInfo uriInfo, String format); } 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 ef2b8a09c..014fba7cc 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 @@ -18,8 +18,6 @@ */ package org.apache.olingo.server.api.processor; -import java.util.List; - import org.apache.olingo.commons.api.edm.Edm; import org.apache.olingo.server.api.OData; diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/api/ODataRequestTest.java b/lib/server-api/src/test/java/org/apache/olingo/server/api/ODataRequestTest.java similarity index 100% rename from lib/server-test/src/test/java/org/apache/olingo/server/api/ODataRequestTest.java rename to lib/server-api/src/test/java/org/apache/olingo/server/api/ODataRequestTest.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 new file mode 100644 index 000000000..baf6d7b0c --- /dev/null +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ContentNegotiator.java @@ -0,0 +1,132 @@ +/* + * 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.core; + +import java.util.ArrayList; +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.server.api.ODataRequest; +import org.apache.olingo.server.api.processor.CustomContentTypeSupport; +import org.apache.olingo.server.api.processor.FormatContentTypeMapping; +import org.apache.olingo.server.api.processor.MetadataProcessor; +import org.apache.olingo.server.api.processor.Processor; +import org.apache.olingo.server.api.uri.queryoption.FormatOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ContentNegotiator { + + private final static Logger LOG = LoggerFactory.getLogger(ContentNegotiator.class); + + private List getDefaultSupportedContentTypes(Class processorClass) { + List defaults = new ArrayList(); + + if (processorClass == MetadataProcessor.class) { + defaults.add(new FormatContentTypeMapping("xml", ContentType.APPLICATION_XML.toContentTypeString())); + } + else { + defaults.add(new FormatContentTypeMapping("json", ContentType.APPLICATION_JSON.toContentTypeString())); + } + + return defaults; + } + + public List getSupportedContentTypes(Processor processor, + Class processorClass) { + + List supportedContentTypes = getDefaultSupportedContentTypes(processorClass); + + if (processor instanceof CustomContentTypeSupport) { + supportedContentTypes = + ((CustomContentTypeSupport) processor).modifySupportedContentTypes(supportedContentTypes, processorClass); + } + + return supportedContentTypes; + } + + public String doContentNegotiation(FormatOption formatOption, ODataRequest request, + List supportedContentTypes) { + String requestedContentType = null; + + List acceptHeaderValues = request.getHeader(HttpHeader.ACCEPT); + + boolean supported = false; + + if (formatOption != null) { + + if ("json".equalsIgnoreCase(formatOption.getText())) { + requestedContentType = HttpContentType.APPLICATION_JSON; + for (FormatContentTypeMapping entry : supportedContentTypes) { + if (requestedContentType.equalsIgnoreCase(entry.getContentType())){ + supported = true; + break; + } + } + } else { + requestedContentType = formatOption.getText(); + for (FormatContentTypeMapping entry : supportedContentTypes) { + if (requestedContentType.equalsIgnoreCase(entry.getFormatAlias())){ + supported = true; + break; + } + } + } + } else if (acceptHeaderValues != null) { + List acceptedContentTypes = new ArrayList(); + +// for (String acceptHeaderValue : acceptHeaderValues) { +// acceptedContentTypes.addAll(parseAcceptHeader(acceptHeaderValue)); +// } + + for (String acceptedContentType : acceptedContentTypes) { +// if (isContentTypeSupported(acceptedContentType, supportedContentTypes)) { +// requestedContentType = acceptedContentType; +// } + } + + if (requestedContentType == null) { + throw new RuntimeException("unsupported accept content type: " + acceptedContentTypes + " != " + + supportedContentTypes); + } + + requestedContentType = null; + } else { + requestedContentType = HttpContentType.APPLICATION_JSON; + for (FormatContentTypeMapping entry : supportedContentTypes) { + if (requestedContentType.equalsIgnoreCase(entry.getContentType())){ + supported = true; + break; + } + } + } + + if (!supported) { + throw new RuntimeException("unsupported accept content type: " + requestedContentType + " != " + + supportedContentTypes); + } + + LOG.debug("requested content type: " + requestedContentType); + + return requestedContentType; + } +} 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 f7248c392..28bc559fc 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,6 +18,10 @@ */ package org.apache.olingo.server.core; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -25,6 +29,8 @@ import java.util.Map; import org.apache.olingo.commons.api.ODataRuntimeException; import org.apache.olingo.commons.api.edm.Edm; import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion; +import org.apache.olingo.commons.api.format.AcceptType; +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; @@ -32,20 +38,27 @@ 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.processor.CollectionProcessor; +import org.apache.olingo.server.api.processor.FormatContentTypeMapping; import org.apache.olingo.server.api.processor.DefaultProcessor; import org.apache.olingo.server.api.processor.EntityProcessor; 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.processor.CustomContentTypeSupport; import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.api.uri.UriResource; import org.apache.olingo.server.api.uri.UriResourceNavigation; import org.apache.olingo.server.api.uri.UriResourcePartTyped; +import org.apache.olingo.server.api.uri.queryoption.FormatOption; import org.apache.olingo.server.core.uri.parser.Parser; import org.apache.olingo.server.core.uri.validator.UriValidator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ODataHandler { + private final static Logger LOG = LoggerFactory.getLogger(ODataHandler.class); + private final OData odata; private final Edm edm; private Map, Processor> processors = new HashMap, Processor>(); @@ -72,12 +85,17 @@ public class ODataHandler { UriValidator validator = new UriValidator(); validator.validate(uriInfo, request.getMethod()); - String requestedContentType = doContentNegotiation(); + String requestedContentType = null; + List supportedContentTypes = null; switch (uriInfo.getKind()) { case metadata: MetadataProcessor mp = selectProcessor(MetadataProcessor.class); - mp.readMetadata(request, response, uriInfo, HttpContentType.APPLICATION_XML); + + /* TODO content negotiation */ + requestedContentType = ContentType.APPLICATION_XML.toContentTypeString(); + + mp.readMetadata(request, response, uriInfo, requestedContentType); break; case service: if ("".equals(request.getRawODataPath())) { @@ -85,11 +103,15 @@ public class ODataHandler { rdp.redirect(request, response); } else { ServiceDocumentProcessor sdp = selectProcessor(ServiceDocumentProcessor.class); + + /* TODO content negotiation */ + requestedContentType = ContentType.APPLICATION_JSON.toContentTypeString(); + sdp.readServiceDocument(request, response, uriInfo, requestedContentType); } break; case resource: - handleResourceDispatching(request, response, uriInfo, requestedContentType); + handleResourceDispatching(request, response, uriInfo); break; default: throw new ODataRuntimeException("not implemented"); @@ -102,27 +124,32 @@ public class ODataHandler { } } - private String doContentNegotiation() { - // TODO: Content Negotiation - return HttpContentType.APPLICATION_JSON; - } - - private void handleResourceDispatching(final ODataRequest request, ODataResponse response, UriInfo uriInfo, - String requestedContentType) { + private void handleResourceDispatching(final ODataRequest request, ODataResponse response, UriInfo uriInfo) { int lastPathSegmentIndex = uriInfo.getUriResourceParts().size() - 1; UriResource lastPathSegment = uriInfo.getUriResourceParts().get(lastPathSegmentIndex); + String requestedContentType = null; + List supportedContentTypes = null; + switch (lastPathSegment.getKind()) { case entitySet: if (((UriResourcePartTyped) lastPathSegment).isCollection()) { if (request.getMethod().equals(HttpMethod.GET)) { - CollectionProcessor esp = selectProcessor(CollectionProcessor.class); - esp.readCollection(request, response, uriInfo, requestedContentType); + CollectionProcessor cp = selectProcessor(CollectionProcessor.class); + + /* TODO content negotiation */ + requestedContentType = ContentType.APPLICATION_JSON.toContentTypeString(); + + cp.readCollection(request, response, uriInfo, requestedContentType); } else { throw new ODataRuntimeException("not implemented"); } } else { if (request.getMethod().equals(HttpMethod.GET)) { EntityProcessor ep = selectProcessor(EntityProcessor.class); + + /* TODO content negotiation */ + requestedContentType = ContentType.APPLICATION_JSON.toContentTypeString(); + ep.readEntity(request, response, uriInfo, requestedContentType); } else { throw new ODataRuntimeException("not implemented"); @@ -132,14 +159,22 @@ public class ODataHandler { case navigationProperty: if (((UriResourceNavigation) lastPathSegment).isCollection()) { if (request.getMethod().equals(HttpMethod.GET)) { - CollectionProcessor esp = selectProcessor(CollectionProcessor.class); - esp.readCollection(request, response, uriInfo, requestedContentType); + CollectionProcessor cp = selectProcessor(CollectionProcessor.class); + + /* TODO content negotiation */ + requestedContentType = ContentType.APPLICATION_JSON.toContentTypeString(); + + cp.readCollection(request, response, uriInfo, requestedContentType); } else { throw new ODataRuntimeException("not implemented"); } } else { if (request.getMethod().equals(HttpMethod.GET)) { EntityProcessor ep = selectProcessor(EntityProcessor.class); + + /* TODO content negotiation */ + requestedContentType = ContentType.APPLICATION_JSON.toContentTypeString(); + ep.readEntity(request, response, uriInfo, requestedContentType); } else { throw new ODataRuntimeException("not implemented"); 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 36ef5c59d..1b15995a1 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 @@ -182,7 +182,7 @@ public class ODataHttpHandlerImpl implements ODataHttpHandler { odRequest.setRawServiceResolutionUri(rawServiceResolutionUri); } - private void extractHeaders(ODataRequest odRequest, final HttpServletRequest req) { + static void extractHeaders(ODataRequest odRequest, final HttpServletRequest req) { for (Enumeration headerNames = req.getHeaderNames(); headerNames.hasMoreElements();) { String headerName = (String) headerNames.nextElement(); List headerValues = new ArrayList(); 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 new file mode 100644 index 000000000..ba5802bdc --- /dev/null +++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/ContentNegotiatorTest.java @@ -0,0 +1,327 @@ +/* + * 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.core; + +import static org.mockito.Mockito.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.olingo.commons.api.edm.Edm; +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.processor.CollectionProcessor; +import org.apache.olingo.server.api.processor.CustomContentTypeSupport; +import org.apache.olingo.server.api.processor.FormatContentTypeMapping; +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.uri.queryoption.FormatOption; +import org.apache.olingo.server.api.uri.queryoption.SystemQueryOptionKind; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ContentNegotiatorTest { + + static final private String ACCEPT_CASE_JSON = "application/json;odata=verbose;q=0.2"; + static final private String ACCEPT_CASE_XML = "application/xml"; + static final private String ACCEPT_CASE_TEXT = "text/plain;q=0.5"; + static final private String ACCEPT_CASE_MULTI = "text/plain;q=0.5,application/aaa;q=0.4"; + + //@formatter:off (Eclipse formatter) + //CHECKSTYLE:OFF (Maven checkstyle) + + String[][] casesServiceDocument = { + /* expected $format accept alias ct mapping */ + { "application/json", null, null, null ,null }, + { "application/json", "json", null, null ,null }, + { "application/json", "json", "application/json", null ,null }, + { "a", "a", null, "a" ,"a/a" }, + +// { "application/json", null, "application/json", null ,null }, +// { "application/json", null, ACCEPT_CASE_JSON, null ,null }, +// { "application/json", null, "*/*", null ,null }, +// { "a/a", "a", null, "a, b" ,"a/a,b/b" }, +// { "a", null, "*/*", "a, b" ,null }, +// { "a", "a", "*/*", "a, b" ,null }, + }; + + String[][] casesMetadata = { + /* expected $format accept alias ct mapping */ + { "application/xml", null, null, null ,null }, + { "application/xml", "xml", null, null ,null }, + { "application/xml", null, "application/xml", null ,null }, + { "application/xml", "xml", "application/xml", null ,null }, + { "application/xml", null, ACCEPT_CASE_XML, null ,null }, + { "application/xml", null, "*/*", null ,null }, + { "a", "a", null, "a, b" ,null }, + { "a", "a", null, "a, b" ,null }, + { "a", null, "*/*", "a, b" ,null }, + { "a", "a", "*/*", "a, b" ,null }, + }; + +// String[][] casesEntitySet = { +// /* expected $format accept supported $formatmapping */ +// { "application/json", null, null, null ,null }, +// { "application/json", "json", null, null ,null }, +// { "application/json", "json", "application/json", null ,null }, +// { "application/json", null, "application/json", null ,null }, +// { "application/json", null, ACCEPT_CASE_JSON, null ,null }, +// { "application/json", null, "*/*", null ,null }, +// { "a", "a", null, "a, b" ,null }, +// { "a", null, "*/*", "a, b" ,null }, +// { "a", "a", "*/*", "a, b" ,null }, +// }; + //CHECKSTYLE:ON + //@formatter:on + + private final static Logger LOG = LoggerFactory.getLogger(ContentNegotiatorTest.class); + + @Test + public void testServiceDocumentSingleCase() { + String[] useCase = { "application/json", null, null, null, null }; + + testContentNegotiation(useCase, ServiceDocumentProcessor.class); + } + + @Test + public void testServiceDocumentDefault() { + for (String[] useCase : casesServiceDocument) { + testContentNegotiation(useCase, ServiceDocumentProcessor.class); + } + } + + public void testContentNegotiation(String[] useCase, Class processorClass) { + + LOG.debug(Arrays.asList(useCase).toString()); + + ODataRequest request = new ODataRequest(); + request.setMethod(HttpMethod.GET); + request.setRawODataPath("/" + (useCase[1] == null ? "" : "?$format=" + useCase[1])); + + ContentNegotiator cn = new ContentNegotiator(); + + ProcessorStub p = new ProcessorStub(createCustomContentTypeMapping(useCase[3], useCase[4])); + + List supportedContentTypes = + cn.getSupportedContentTypes(p, processorClass); + + FormatOption fo = null; + if (useCase[1] != null) { + fo = mock(FormatOption.class); + when(fo.getText()).thenReturn(useCase[1]); + } + + String requestedContentType = cn.doContentNegotiation(fo, request, supportedContentTypes); + + assertNotNull(requestedContentType); + assertEquals(useCase[0], requestedContentType); + } + + private List createCustomContentTypeMapping(String formatString, String contentTypeString) { + List map = null; + + assertTrue(!(formatString == null ^ contentTypeString == null)); + + if (formatString != null) { + String[] formats = formatString.split(","); + String[] contentTypes = contentTypeString.split(","); + + assertEquals(formats.length, contentTypes.length); + + map = new ArrayList(); + for (int i = 0; i < formats.length; i++) { + map.add(new FormatContentTypeMapping(formats[i], contentTypes[i])); + } + } + + return map; + } + + @Test + @Ignore + public void testMetadataDefault() { + + for (String[] useCase : casesMetadata) { + ODataRequest request = new ODataRequest(); + request.setMethod(HttpMethod.GET); + request.setRawODataPath("/$metadata" + (useCase[1] == null ? "" : "?$format=" + useCase[1])); + +// ODataResponse response = callHandler(useCase, request); +// +// assertEquals(useCase[0], response.getHeaders().get(HttpHeader.CONTENT_TYPE)); + } + } + +// @Test +// public void testEntitySet() { +// +// for (String[] useCase : casesEntitySet) { +// ODataRequest request = new ODataRequest(); +// request.setMethod(HttpMethod.GET); +// request.setRawODataPath("/ESAllPrim" + (useCase[1] == null ? "" : "?$format=" + useCase[1])); +// +// ODataResponse response = callHandler(useCase, request, new CollectionProcessorStub()); +// +// assertEquals(useCase[0], response.getHeaders().get(HttpHeader.CONTENT_TYPE)); +// } +// } + +// private ODataResponse callHandler(String[] useCase, ODataRequest request, +// Processor defaultProcessor) { +// ODataHandler handler = createHandler(); +// +// if (useCase[2] != null) { +// request.addHeader(HttpHeader.ACCEPT, Arrays.asList(useCase[2])); +// } +// +// if (useCase[3] != null) { +// String[] aliase = useCase[3].split(","); +// String[] mappings = useCase[4].split(","); +// +// FormatContentTypeMapping[] formatCTMap = new FormatContentTypeMapping[aliase.length]; +// +// for(int i=0; i< formatCTMap.length; i++) { +// formatCTMap[i] = new FormatContentTypeMapping(aliase[i], mappings[i]); +// } +// +// +// ProcessorStub stub = new ProcessorStub(formatCTMap); +// handler.register(stub); +// } else { +// if (defaultProcessor != null) { +// handler.register(defaultProcessor); +// } +// } +// +// ODataResponse response = handler.process(request); +// return response; +// } + +// ODataResponse callHandler(String[] useCase, ODataRequest request) { +// return callHandler(useCase, request, null); +// } + + private class CollectionProcessorStub implements CollectionProcessor { + + @Override + public void init(OData odata, Edm edm) {} + + @Override + public void readCollection(ODataRequest request, ODataResponse response, UriInfo uriInfo, String format) { + response.setHeader(HttpHeader.CONTENT_TYPE, format); + } + } + + @Test + public void testDefaultSupportedContentTypesServiceDocument() { + ContentNegotiator cn = new ContentNegotiator(); + + ProcessorStub p = new ProcessorStub(null); + + List supportedContentTypes = + cn.getSupportedContentTypes(p, ServiceDocumentProcessor.class); + + assertNotNull(supportedContentTypes); + assertEquals(1, supportedContentTypes.size()); + assertEquals("json", supportedContentTypes.get(0).getFormatAlias()); + assertEquals("application/json", supportedContentTypes.get(0).getContentType()); + } + + @Test + public void testDefaultSupportedContentTypesMetadata() { + ContentNegotiator cn = new ContentNegotiator(); + + ProcessorStub p = new ProcessorStub(null); + + List supportedContentTypes = cn.getSupportedContentTypes(p, MetadataProcessor.class); + + assertNotNull(supportedContentTypes); + assertEquals(1, supportedContentTypes.size()); + assertEquals("xml", supportedContentTypes.get(0).getFormatAlias()); + assertEquals("application/xml", supportedContentTypes.get(0).getContentType()); + } + + @Test + public void testCustomSupportedContentTypesServiceDocument() { + ContentNegotiator cn = new ContentNegotiator(); + + ProcessorStub p = new ProcessorStub(Arrays.asList(new FormatContentTypeMapping("a", "a/a"))); + + List supportedContentTypes = + cn.getSupportedContentTypes(p, ServiceDocumentProcessor.class); + + assertNotNull(supportedContentTypes); + assertEquals(2, supportedContentTypes.size()); + assertEquals("json", supportedContentTypes.get(0).getFormatAlias()); + assertEquals("application/json", supportedContentTypes.get(0).getContentType()); + assertEquals("a", supportedContentTypes.get(1).getFormatAlias()); + assertEquals("a/a", supportedContentTypes.get(1).getContentType()); + } + + private class ProcessorStub implements ServiceDocumentProcessor, MetadataProcessor, + CollectionProcessor, + CustomContentTypeSupport { + + List customMapping; + + ProcessorStub(List mapping) { + this.customMapping = mapping; + } + + @Override + public void init(OData odata, Edm edm) {} + + @Override + public List modifySupportedContentTypes( + List supportedContentTypes, + Class processorClass) { + if (customMapping != null) { + supportedContentTypes.addAll(customMapping); + } + return supportedContentTypes; + } + + @Override + public void readServiceDocument(ODataRequest request, ODataResponse response, UriInfo uriInfo, String format) { + response.setHeader(HttpHeader.CONTENT_TYPE, format); + } + + @Override + public void readCollection(ODataRequest request, ODataResponse response, UriInfo uriInfo, String format) { + response.setHeader(HttpHeader.CONTENT_TYPE, format); + } + + @Override + public void readMetadata(ODataRequest request, ODataResponse response, UriInfo uriInfo, String format) { + response.setHeader(HttpHeader.CONTENT_TYPE, format); + } + + } +} diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/ODataHttpHandlerImplTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/ODataHttpHandlerImplTest.java index faa82e321..371507643 100644 --- a/lib/server-core/src/test/java/org/apache/olingo/server/core/ODataHttpHandlerImplTest.java +++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/ODataHttpHandlerImplTest.java @@ -23,6 +23,8 @@ import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.util.Arrays; + import javax.servlet.http.HttpServletRequest; import org.apache.olingo.commons.api.ODataRuntimeException; diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/ContentNegotiationTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/ContentNegotiationTest.java deleted file mode 100644 index db5b9847a..000000000 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/ContentNegotiationTest.java +++ /dev/null @@ -1,201 +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.core; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.withSettings; - -import java.util.Arrays; -import java.util.List; - -import org.apache.olingo.commons.api.edm.Edm; -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.processor.CollectionProcessor; -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.processor.SupportCustomContentTypes; -import org.apache.olingo.server.api.uri.UriInfo; -import org.apache.olingo.server.tecsvc.provider.EdmTechProvider; -import org.junit.Ignore; -import org.junit.Test; - -public class ContentNegotiationTest { - -// static final private String ACCEPT_CASE1 = "text/plain;q=0.5"; -// static final private String ACCEPT_CASE2 = "application/json;odata=verbose;q=0.2"; - - //@formatter:off (Eclipse formatter) - //CHECKSTYLE:OFF (Maven checkstyle) - - String[][] casesServiceDocument = { - /* expected $format accept supported */ - { "application/json", null, null, null }, - { "application/json", "json", null, null }, - { "application/json", "json", "application/json", null }, - { "application/json", null, "application/json", null }, - { "application/json", null, "*/*", null }, -// { "aaa", "aaa", null, "aaa, bbb" }, -// { "aaa", null, "*/*", "aaa, bbb" }, - }; - - String[][] casesMetadata = { - /* expected $format accept supported */ - { "application/xml", null, null, null }, - { "application/xml", "xml", null, null }, - { "application/xml", null, "application/xml", null }, - { "application/xml", "xml", "application/xml", null }, - { "application/xml", null, "*/*", null }, -// { "aaa", "aaa", null, "aaa, bbb" }, - }; - - String[][] casesEntitySet = { - /* expected $format accept supported */ - { "application/json", null, null, null }, - { "application/json", "json", null, null }, - { "application/json", "json", "application/json", null }, - { "application/json", null, "*/*", null }, - }; - - //CHECKSTYLE:ON - //@formatter:on - - private ODataHandler createHandler() { - OData odata = OData.newInstance(); - Edm edm = odata.createEdm(new EdmTechProvider()); - return new ODataHandler(odata, edm); - - } - - @Test - public void testServiceDocumentDefault() { - - for (String[] useCase : casesServiceDocument) { - ODataRequest request = new ODataRequest(); - request.setMethod(HttpMethod.GET); - request.setRawODataPath("/" + (useCase[1] == null ? "" : "?$format=" + useCase[1])); - - ODataResponse response = callHandler(useCase, request); - - assertEquals(useCase[0], response.getHeaders().get(HttpHeader.CONTENT_TYPE)); - } - } - - @Test - public void testMetadataDefault() { - - for (String[] useCase : casesMetadata) { - ODataRequest request = new ODataRequest(); - request.setMethod(HttpMethod.GET); - request.setRawODataPath("/$metadata" + (useCase[1] == null ? "" : "?$format=" + useCase[1])); - - ODataResponse response = callHandler(useCase, request); - - assertEquals(useCase[0], response.getHeaders().get(HttpHeader.CONTENT_TYPE)); - } - } - - @Test - public void testEntitySet() { - - for (String[] useCase : casesEntitySet) { - ODataRequest request = new ODataRequest(); - request.setMethod(HttpMethod.GET); - request.setRawODataPath("/ESAllPrim" + (useCase[1] == null ? "" : "?$format=" + useCase[1])); - - ODataResponse response = callHandler(useCase, request, new CollectionProcessorStub()); - - assertEquals(useCase[0], response.getHeaders().get(HttpHeader.CONTENT_TYPE)); - } - } - - private ODataResponse callHandler(String[] useCase, ODataRequest request, - Processor defaultProcessor) { - ODataHandler handler = createHandler(); - - if (useCase[3] != null) { - ProcessorStub stub = new ProcessorStub(useCase[3].split(",")); - handler.register(stub); - } else { - if (defaultProcessor != null) { - handler.register(defaultProcessor); - } - } - - ODataResponse response = handler.process(request); - return response; - } - - ODataResponse callHandler(String[] useCase, ODataRequest request) { - return callHandler(useCase, request, null); - } - - private class ProcessorStub implements ServiceDocumentProcessor, MetadataProcessor, CollectionProcessor, - SupportCustomContentTypes { - - String[] formats; - - ProcessorStub(String[] strings) { - this.formats = strings; - } - - @Override - public void init(OData odata, Edm edm) {} - - @Override - public List getSupportedContentTypes(Class processorClass) { - return Arrays.asList(formats); - } - - @Override - public void readServiceDocument(ODataRequest request, ODataResponse response, UriInfo uriInfo, String format) { - response.setHeader(HttpHeader.CONTENT_TYPE, format); - } - - @Override - public void readCollection(ODataRequest request, ODataResponse response, UriInfo uriInfo, String format) { - response.setHeader(HttpHeader.CONTENT_TYPE, format); - } - - @Override - public void readMetadata(ODataRequest request, ODataResponse response, UriInfo uriInfo, String format) { - response.setHeader(HttpHeader.CONTENT_TYPE, format); - } - - } - - private class CollectionProcessorStub implements CollectionProcessor { - - @Override - public void init(OData odata, Edm edm) {} - - @Override - public void readCollection(ODataRequest request, ODataResponse response, UriInfo uriInfo, String format) { - response.setHeader(HttpHeader.CONTENT_TYPE, format); - } - } - -}