diff --git a/lib/server-api/src/main/java/org/apache/olingo/netty/server/api/ODataNetty.java b/lib/server-api/src/main/java/org/apache/olingo/netty/server/api/ODataNetty.java
new file mode 100644
index 000000000..a61b160df
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/netty/server/api/ODataNetty.java
@@ -0,0 +1,56 @@
+/*
+ * 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.netty.server.api;
+
+import org.apache.olingo.commons.api.ex.ODataRuntimeException;
+import org.apache.olingo.server.api.OData;
+import org.apache.olingo.server.api.ServiceMetadata;
+
+public abstract class ODataNetty extends OData {
+
+ private static final String IMPLEMENTATION = "org.apache.olingo.netty.server.core.ODataNettyImpl";
+
+ /**
+ * Use this method to create a new OData instance. Each thread/request should keep its own instance.
+ * @return a new OData instance
+ */
+ public static ODataNetty newInstance() {
+ try {
+ final Class> clazz = Class.forName(ODataNetty.IMPLEMENTATION);
+
+
+ /* We explicitly do not use the singleton pattern to keep the server state free
+ * and avoid class loading issues also during hot deployment.*/
+
+ final Object object = clazz.newInstance();
+
+ return (ODataNetty) object;
+
+ } catch (final Exception e) {
+ throw new ODataRuntimeException(e);
+ }
+ }
+ /**
+ * Creates a new ODataNettyHandler for handling OData requests in an HTTP context.
+ *
+ * @param serviceMetadata - metadata object required to handle an OData request
+ */
+ public abstract ODataNettyHandler createNettyHandler(ServiceMetadata serviceMetadata);
+
+}
diff --git a/lib/server-api/src/main/java/org/apache/olingo/netty/server/api/ODataNettyHandler.java b/lib/server-api/src/main/java/org/apache/olingo/netty/server/api/ODataNettyHandler.java
new file mode 100644
index 000000000..414ba923a
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/netty/server/api/ODataNettyHandler.java
@@ -0,0 +1,47 @@
+/*
+ * 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.netty.server.api;
+
+import java.util.Map;
+
+import org.apache.olingo.server.api.processor.Processor;
+
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.HttpResponse;
+
+public interface ODataNettyHandler {
+
+ /**
+ *
Processes a NettyRequest as an OData request.
+ * This includes URI parsing, content negotiation, dispatching the request
+ * to a specific custom processor implementation for handling data and
+ * creating the serialized content for the response object.
+ * @param request - must be a HTTP OData request
+ * @param response - HTTP OData response
+ */
+ void processNettyRequest(HttpRequest request, HttpResponse response, Map requestParameters);
+
+ /**
+ * Registers additional custom processor implementations for handling OData requests.
+ * If request processing requires a processor that is not registered then a
+ * "not implemented" exception will happen.
+ */
+ void register(Processor processor);
+
+}
diff --git a/lib/server-core/src/main/java/org/apache/olingo/netty/server/core/ODataNettyHandlerImpl.java b/lib/server-core/src/main/java/org/apache/olingo/netty/server/core/ODataNettyHandlerImpl.java
new file mode 100644
index 000000000..add64095a
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/netty/server/core/ODataNettyHandlerImpl.java
@@ -0,0 +1,334 @@
+/*
+ * 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.netty.server.core;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channel;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.olingo.commons.api.ex.ODataRuntimeException;
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.netty.server.api.ODataNettyHandler;
+import org.apache.olingo.server.api.OData;
+import org.apache.olingo.server.api.ODataContent;
+import org.apache.olingo.server.api.ODataLibraryException;
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.ODataServerError;
+import org.apache.olingo.server.api.ServiceMetadata;
+import org.apache.olingo.server.api.processor.Processor;
+import org.apache.olingo.server.core.ODataExceptionHelper;
+import org.apache.olingo.server.core.ODataHandlerException;
+import org.apache.olingo.server.core.ODataHandlerImpl;
+import org.apache.olingo.server.core.debug.ServerCoreDebugger;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufInputStream;
+import io.netty.buffer.ByteBufOutputStream;
+import io.netty.handler.codec.http.HttpContent;
+import io.netty.handler.codec.http.HttpMessage;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.HttpResponse;
+import io.netty.handler.codec.http.HttpResponseStatus;
+
+public class ODataNettyHandlerImpl implements ODataNettyHandler {
+
+ public static final int COPY_BUFFER_SIZE = 8192;
+
+ private final ODataHandlerImpl handler;
+ private final ServerCoreDebugger debugger;
+
+ private static final String CONTEXT_PATH = "contextPath";
+ private static final String SPLIT = "split";
+
+ private int split = 0;
+
+ public ODataNettyHandlerImpl(final OData odata, final ServiceMetadata serviceMetadata) {
+ debugger = new ServerCoreDebugger(odata);
+ handler = new ODataHandlerImpl(odata, serviceMetadata, debugger);
+ }
+
+ private ODataResponse handleException(final ODataRequest odRequest, final Exception e) {
+ ODataResponse resp = new ODataResponse();
+ ODataServerError serverError;
+ if (e instanceof ODataHandlerException) {
+ serverError = ODataExceptionHelper.createServerErrorObject((ODataHandlerException) e, null);
+ } else if (e instanceof ODataLibraryException) {
+ serverError = ODataExceptionHelper.createServerErrorObject((ODataLibraryException) e, null);
+ } else {
+ serverError = ODataExceptionHelper.createServerErrorObject(e);
+ }
+ handler.handleException(odRequest, resp, serverError, e);
+ return resp;
+ }
+
+ /**
+ * Convert the OData Response to Netty Response
+ * @param response
+ * @param odResponse
+ */
+ static void convertToHttp(final HttpResponse response, final ODataResponse odResponse) {
+ response.setStatus(HttpResponseStatus.valueOf(odResponse.getStatusCode()));
+
+ for (Entry> entry : odResponse.getAllHeaders().entrySet()) {
+ for (String headerValue : entry.getValue()) {
+ ((HttpMessage)response).headers().add(entry.getKey(), headerValue);
+ }
+ }
+
+ if (odResponse.getContent() != null) {
+ copyContent(odResponse.getContent(), response);
+ } else if (odResponse.getODataContent() != null) {
+ writeContent(odResponse, response);
+ }
+ }
+
+ /**
+ * Write the odata content to netty response content
+ * @param odataResponse
+ * @param response
+ */
+ static void writeContent(final ODataResponse odataResponse, final HttpResponse response) {
+ ODataContent res = odataResponse.getODataContent();
+ res.write(Channels.newChannel(new ByteBufOutputStream(((HttpContent)response).content())));
+ }
+
+ static void copyContent(final InputStream inputStream, final HttpResponse response) {
+ copyContent(Channels.newChannel(inputStream), response);
+ }
+
+ /**
+ * Copy OData content to netty content
+ * @param input
+ * @param response
+ */
+ static void copyContent(final ReadableByteChannel input, final HttpResponse response) {
+ WritableByteChannel output = null;
+ try {
+ ByteBuffer inBuffer = ByteBuffer.allocate(COPY_BUFFER_SIZE);
+ output = Channels.newChannel(new ByteBufOutputStream(((HttpContent)response).content()));
+ while (input.read(inBuffer) > 0) {
+ inBuffer.flip();
+ output.write(inBuffer);
+ inBuffer.clear();
+ }
+ } catch (IOException e) {
+ throw new ODataRuntimeException("Error on reading request content", e);
+ } finally {
+ closeStream(input);
+ closeStream(output);
+ }
+ }
+
+ private static void closeStream(final Channel closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+
+ /**
+ * Extract the information part of Netty Request and fill OData Request
+ * @param odRequest
+ * @param httpRequest
+ * @param split
+ * @param contextPath
+ * @return
+ * @throws ODataLibraryException
+ */
+ private ODataRequest fillODataRequest(final ODataRequest odRequest, final HttpRequest httpRequest,
+ final int split, final String contextPath) throws ODataLibraryException {
+ final int requestHandle = debugger.startRuntimeMeasurement("ODataHttpHandlerImpl", "fillODataRequest");
+ try {
+ ByteBuf byteBuf = ((HttpContent)httpRequest).content();
+ ByteBufInputStream inputStream = new ByteBufInputStream(byteBuf);
+ odRequest.setBody(inputStream);
+
+ odRequest.setProtocol(httpRequest.protocolVersion().text());
+ odRequest.setMethod(extractMethod(httpRequest));
+ int innerHandle = debugger.startRuntimeMeasurement("ODataNettyHandlerImpl", "copyHeaders");
+ copyHeaders(odRequest, httpRequest);
+ debugger.stopRuntimeMeasurement(innerHandle);
+ innerHandle = debugger.startRuntimeMeasurement("ODataNettyHandlerImpl", "fillUriInformation");
+ fillUriInformationFromHttpRequest(odRequest, httpRequest, split, contextPath);
+ debugger.stopRuntimeMeasurement(innerHandle);
+
+ return odRequest;
+ } finally {
+ debugger.stopRuntimeMeasurement(requestHandle);
+ }
+ }
+
+ static HttpMethod extractMethod(final HttpRequest httpRequest) throws ODataLibraryException {
+ final HttpMethod httpRequestMethod;
+ try {
+ httpRequestMethod = HttpMethod.valueOf(httpRequest.method().name());
+ } catch (IllegalArgumentException e) {
+ throw new ODataHandlerException("HTTP method not allowed" +
+ httpRequest.method().name(), e,
+ ODataHandlerException.MessageKeys.HTTP_METHOD_NOT_ALLOWED,
+ httpRequest.method().name());
+ }
+ try {
+ if (httpRequestMethod == HttpMethod.POST) {
+ String xHttpMethod = httpRequest.headers().
+ get(HttpHeader.X_HTTP_METHOD);
+ String xHttpMethodOverride = httpRequest.headers().
+ get(HttpHeader.X_HTTP_METHOD_OVERRIDE);
+
+ if (xHttpMethod == null && xHttpMethodOverride == null) {
+ return httpRequestMethod;
+ } else if (xHttpMethod == null) {
+ return HttpMethod.valueOf(xHttpMethodOverride);
+ } else if (xHttpMethodOverride == null) {
+ return HttpMethod.valueOf(xHttpMethod);
+ } else {
+ if (!xHttpMethod.equalsIgnoreCase(xHttpMethodOverride)) {
+ throw new ODataHandlerException("Ambiguous X-HTTP-Methods",
+ ODataHandlerException.MessageKeys.AMBIGUOUS_XHTTP_METHOD, xHttpMethod, xHttpMethodOverride);
+ }
+ return HttpMethod.valueOf(xHttpMethod);
+ }
+ } else {
+ return httpRequestMethod;
+ }
+ } catch (IllegalArgumentException e) {
+ throw new ODataHandlerException("Invalid HTTP method" +
+ httpRequest.method().name(), e,
+ ODataHandlerException.MessageKeys.INVALID_HTTP_METHOD,
+ httpRequest.method().name());
+ }
+ }
+
+ /**
+ * Fetch the uri information parsing netty request url
+ * @param odRequest
+ * @param httpRequest
+ * @param split
+ * @param contextPath
+ */
+ static void fillUriInformationFromHttpRequest(final ODataRequest odRequest, final HttpRequest httpRequest,
+ final int split, final String contextPath) {
+ String rawRequestUri = httpRequest.uri();
+ if (rawRequestUri.indexOf("?") != -1) {
+ rawRequestUri = rawRequestUri.substring(0, rawRequestUri.indexOf("?"));
+ }
+
+ String rawODataPath;
+ if (!"".equals(contextPath)) {
+ int beginIndex = rawRequestUri.indexOf(contextPath) + contextPath.length();
+ rawODataPath = rawRequestUri.substring(beginIndex);
+ } else {
+ rawODataPath = rawRequestUri;
+ }
+
+ String rawServiceResolutionUri = null;
+ if (split > 0) {
+ rawServiceResolutionUri = rawODataPath;
+ for (int i = 0; i < split; i++) {
+ int index = rawODataPath.indexOf('/', 1);
+ if (-1 == index) {
+ rawODataPath = "";
+ break;
+ } else {
+ rawODataPath = rawODataPath.substring(index);
+ }
+ }
+ int end = rawServiceResolutionUri.length() - rawODataPath.length();
+ rawServiceResolutionUri = rawServiceResolutionUri.substring(0, end);
+ }
+
+ String rawBaseUri = rawRequestUri.substring(0, rawRequestUri.length() - rawODataPath.length());
+
+ int index = httpRequest.uri().indexOf('?');
+ String queryString = null;
+ if (index != -1) {
+ queryString = httpRequest.uri().substring(index + 1);
+ }
+ odRequest.setRawQueryPath(queryString);
+ odRequest.setRawRequestUri(rawRequestUri
+ + (queryString == null ? "" : "?" + queryString));
+ odRequest.setRawODataPath(rawODataPath);
+ odRequest.setRawBaseUri(rawBaseUri);
+ odRequest.setRawServiceResolutionUri(rawServiceResolutionUri);
+ }
+
+ /**
+ * Copy the headers part of Netty Request to OData Request
+ * @param odRequest
+ * @param req
+ */
+ static void copyHeaders(ODataRequest odRequest, final HttpRequest req) {
+ final Set headers = req.headers().names();
+ Iterator headerNames = headers.iterator();
+ while (headerNames.hasNext()) {
+ final String headerName = headerNames.next();
+ final List headerValues = req.headers().getAll(headerName);
+ odRequest.addHeader(headerName, headerValues);
+ }
+ }
+
+@SuppressWarnings("unused")
+@Override
+public void processNettyRequest(HttpRequest request, HttpResponse response,
+ Map requestParameters) {
+ ODataRequest odRequest = new ODataRequest();
+ Exception exception = null;
+ ODataResponse odResponse;
+
+ final int processMethodHandle =
+ debugger.startRuntimeMeasurement("ODataNettyHandlerImpl", "process");
+ try {
+ fillODataRequest(odRequest, request,
+ requestParameters.get(SPLIT) != null? Integer.parseInt(requestParameters.get(SPLIT)) : split,
+ requestParameters.get(CONTEXT_PATH));
+
+ odResponse = process(odRequest);
+ // ALL future methods after process must not throw exceptions!
+ } catch (Exception e) {
+ exception = e;
+ odResponse = handleException(odRequest, e);
+ }
+ debugger.stopRuntimeMeasurement(processMethodHandle);
+
+ convertToHttp(response, odResponse);
+ }
+
+ public ODataResponse process(ODataRequest request) {
+ return handler.process(request);
+ }
+
+ @Override
+ public void register(Processor processor) {
+ handler.register(processor);
+ }
+}
diff --git a/lib/server-core/src/main/java/org/apache/olingo/netty/server/core/ODataNettyImpl.java b/lib/server-core/src/main/java/org/apache/olingo/netty/server/core/ODataNettyImpl.java
new file mode 100644
index 000000000..113eb03b7
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/netty/server/core/ODataNettyImpl.java
@@ -0,0 +1,164 @@
+/*
+ * 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.netty.server.core;
+
+import java.util.Collection;
+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.provider.CsdlEdmProvider;
+import org.apache.olingo.commons.api.edmx.EdmxReference;
+import org.apache.olingo.commons.api.ex.ODataRuntimeException;
+import org.apache.olingo.commons.api.format.ContentType;
+import org.apache.olingo.netty.server.api.ODataNetty;
+import org.apache.olingo.netty.server.api.ODataNettyHandler;
+import org.apache.olingo.server.api.OData;
+import org.apache.olingo.server.api.ODataHandler;
+import org.apache.olingo.server.api.ODataHttpHandler;
+import org.apache.olingo.server.api.ServiceMetadata;
+import org.apache.olingo.server.api.debug.DebugResponseHelper;
+import org.apache.olingo.server.api.deserializer.DeserializerException;
+import org.apache.olingo.server.api.deserializer.FixedFormatDeserializer;
+import org.apache.olingo.server.api.deserializer.ODataDeserializer;
+import org.apache.olingo.server.api.etag.ETagHelper;
+import org.apache.olingo.server.api.etag.ServiceMetadataETagSupport;
+import org.apache.olingo.server.api.prefer.Preferences;
+import org.apache.olingo.server.api.serializer.EdmAssistedSerializer;
+import org.apache.olingo.server.api.serializer.EdmDeltaSerializer;
+import org.apache.olingo.server.api.serializer.FixedFormatSerializer;
+import org.apache.olingo.server.api.serializer.ODataSerializer;
+import org.apache.olingo.server.api.serializer.SerializerException;
+import org.apache.olingo.server.api.uri.UriHelper;
+
+public class ODataNettyImpl extends ODataNetty {
+
+ private static OData odata;
+ private static final String IMPLEMENTATION = "org.apache.olingo.server.core.ODataImpl";
+
+ static {
+ try {
+ final Class> clazz = Class.forName(IMPLEMENTATION);
+
+ /*
+ * We explicitly do not use the singleton pattern to keep the server state free
+ * and avoid class loading issues also during hot deployment.
+ */
+ final Object object = clazz.newInstance();
+ odata = (OData) object;
+ } catch (Exception e) {
+ throw new ODataRuntimeException(e);
+ }
+ }
+
+ @Override
+ public ODataNettyHandler createNettyHandler(ServiceMetadata serviceMetadata) {
+ return new ODataNettyHandlerImpl(this, serviceMetadata);
+ }
+
+ @Override
+ public ODataSerializer createSerializer(ContentType contentType) throws SerializerException {
+ return odata.createSerializer(contentType);
+ }
+
+ @Override
+ public ODataSerializer createSerializer(final ContentType contentType,
+ final List versions) throws SerializerException {
+ return odata.createSerializer(contentType, versions);
+ }
+
+ @Override
+ public FixedFormatSerializer createFixedFormatSerializer() {
+ return odata.createFixedFormatSerializer();
+ }
+
+ @Override
+ public FixedFormatDeserializer createFixedFormatDeserializer() {
+ return odata.createFixedFormatDeserializer();
+ }
+
+ @Override
+ public ODataHttpHandler createHandler(ServiceMetadata serviceMetadata) {
+ return odata.createHandler(serviceMetadata);
+ }
+
+ @Override
+ public ODataHandler createRawHandler(ServiceMetadata serviceMetadata) {
+ return odata.createRawHandler(serviceMetadata);
+ }
+
+ @Override
+ public ServiceMetadata createServiceMetadata(CsdlEdmProvider edmProvider, List references) {
+ return odata.createServiceMetadata(edmProvider, references);
+ }
+
+ @Override
+ public ServiceMetadata createServiceMetadata(CsdlEdmProvider edmProvider, List references,
+ ServiceMetadataETagSupport serviceMetadataETagSupport) {
+ return odata.createServiceMetadata(edmProvider, references, serviceMetadataETagSupport);
+ }
+
+ @Override
+ public UriHelper createUriHelper() {
+ return odata.createUriHelper();
+ }
+
+ @Override
+ public ODataDeserializer createDeserializer(ContentType contentType) throws DeserializerException {
+ return odata.createDeserializer(contentType);
+ }
+
+ @Override
+ public ODataDeserializer createDeserializer(ContentType contentType, ServiceMetadata metadata)
+ throws DeserializerException {
+ return odata.createDeserializer(contentType);
+ }
+
+ @Override
+ public EdmPrimitiveType createPrimitiveTypeInstance(EdmPrimitiveTypeKind kind) {
+ return odata.createPrimitiveTypeInstance(kind);
+ }
+
+ @Override
+ public ETagHelper createETagHelper() {
+ return odata.createETagHelper();
+ }
+
+ @Override
+ public Preferences createPreferences(Collection preferHeaders) {
+ return odata.createPreferences(preferHeaders);
+ }
+
+ @Override
+ public DebugResponseHelper createDebugResponseHelper(String debugFormat) {
+ return odata.createDebugResponseHelper(debugFormat);
+ }
+
+ @Override
+ public EdmAssistedSerializer createEdmAssistedSerializer(ContentType contentType) throws SerializerException {
+ return odata.createEdmAssistedSerializer(contentType);
+ }
+
+ @Override
+ public EdmDeltaSerializer createEdmDeltaSerializer(ContentType contentType, List versions)
+ throws SerializerException {
+ return odata.createEdmDeltaSerializer(contentType, versions);
+ }
+
+}
diff --git a/lib/server-core/src/test/java/org/apache/olingo/netty/server/core/ODataNettyHandlerImplTest.java b/lib/server-core/src/test/java/org/apache/olingo/netty/server/core/ODataNettyHandlerImplTest.java
new file mode 100644
index 000000000..7a1a01a8b
--- /dev/null
+++ b/lib/server-core/src/test/java/org/apache/olingo/netty/server/core/ODataNettyHandlerImplTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.netty.server.core;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.server.api.ODataLibraryException;
+import org.apache.olingo.server.api.ODataRequest;
+import org.junit.Test;
+
+import io.netty.handler.codec.http.HttpHeaders;
+import io.netty.handler.codec.http.HttpRequest;
+
+public class ODataNettyHandlerImplTest {
+
+ @Test
+ public void extractMethodForNettyRequest() throws Exception {
+ String[][] mm = {
+ { "GET", null, null, "GET" },
+ { "GET", "xxx", "yyy", "GET" },
+ { "PUT", "xxx", "yyy", "PUT" },
+ { "DELETE", "xxx", "yyy", "DELETE" },
+ { "PATCH", "xxx", "yyy", "PATCH" },
+
+ { "POST", null, null, "POST" },
+ { "POST", null, "GET", "GET" },
+ { "POST", null, "PATCH", "PATCH" },
+
+ { "POST", "GET", null, "GET" },
+ { "POST", "PATCH", null, "PATCH" },
+
+ { "POST", "GET", "GET", "GET" },
+ { "HEAD", null, null, "HEAD" }
+ };
+
+ for (String[] m : mm) {
+
+ HttpRequest hr = mock(HttpRequest.class);
+ io.netty.handler.codec.http.HttpMethod hm = mock(io.netty.handler.codec.http.HttpMethod.class);
+ when(hr.method()).thenReturn(hm);
+ when(hm.name()).thenReturn(m[0]);
+ HttpHeaders hh = mock(HttpHeaders.class);
+ when(hr.headers()).thenReturn(hh);
+ when(hh.get("X-HTTP-Method")).thenReturn(m[1]);
+ when(hh.get("X-HTTP-Method-Override")).thenReturn(m[2]);
+
+ assertEquals(HttpMethod.valueOf(m[3]), ODataNettyHandlerImpl.extractMethod(hr));
+ }
+ }
+
+ @Test
+ public void extractMethodFailForNettyRequest() throws Exception {
+ String[][] mm = {
+ { "POST", "bla", null },
+ { "POST", "PUT", "PATCH" },
+ { "OPTIONS", null, null }
+ };
+
+ for (String[] m : mm) {
+
+ HttpRequest hr = mock(HttpRequest.class);
+ io.netty.handler.codec.http.HttpMethod hm = mock(io.netty.handler.codec.http.HttpMethod.class);
+ when(hr.method()).thenReturn(hm);
+
+ when(hm.name()).thenReturn(m[0]);
+ HttpHeaders hh = mock(HttpHeaders.class);
+ when(hr.headers()).thenReturn(hh);
+ when(hh.get("X-HTTP-Method")).thenReturn(m[1]);
+ when(hh.get("X-HTTP-Method-Override")).thenReturn(m[2]);
+
+ try {
+ ODataNettyHandlerImpl.extractMethod(hr);
+ fail();
+ } catch (ODataLibraryException e) {
+ // expected
+ }
+ }
+ }
+
+ @Test
+ public void extractUriForNettyRequests() {
+
+ //@formatter:off (Eclipse formatter)
+ //CHECKSTYLE:OFF (Maven checkstyle)
+ String [][] uris = {
+ /* 0: cp 1: sr 2: od 3: qp 4: spl */
+ { "", "", "", "", "0"},
+ { "", "", "/", "", "0"},
+ { "", "", "/od", "", "0"},
+ { "", "", "/od/", "", "0"},
+
+ { "/cp", "", "", "", "0"},
+ { "/cp", "", "/", "", "0"},
+ { "/cp", "", "/od", "", "0"},
+ { "", "/sr", "", "", "1"},
+ { "", "/sr", "/", "", "1"},
+ { "", "/sr", "/od", "", "1"},
+ { "", "/sr/sr", "", "", "2"},
+ { "", "/sr/sr", "/", "", "2"},
+ { "", "/sr/sr", "/od", "", "2"},
+
+ { "/cp", "/sr", "/", "", "1"},
+ { "/cp", "/sr", "/od", "", "1"},
+
+ { "", "", "", "qp", "0"},
+ { "", "", "/", "qp", "0"},
+ { "/cp", "/sr", "/od", "qp", "1"},
+
+ { "/c%20p", "/s%20r", "/o%20d", "p+q", "1"},
+ };
+ //@formatter:on
+ // CHECKSTYLE:on
+
+ for (String[] p : uris) {
+ HttpRequest hr = mock(HttpRequest.class);
+
+ String requestUrl = p[0] + p[1] + p[2];
+ if (!p[3].equals("") || p[3].length() > 0) {
+ requestUrl += "?$" + p[3];
+ }
+
+ when(hr.uri()).thenReturn(requestUrl);
+
+ ODataRequest odr = new ODataRequest();
+ ODataNettyHandlerImpl.fillUriInformationFromHttpRequest(odr, hr, Integer.parseInt(p[4]), p[0]);
+
+ String rawBaseUri = p[0] + p[1];
+ String rawODataPath = p[2];
+ String rawQueryPath = "".equals(p[3]) ? null : "$" + p[3];
+ String rawRequestUri = p[0] + p[1] + p[2] + ("".equals(p[3]) ? "" : "?$" + p[3]);
+ String rawServiceResolutionUri = "".equals(p[1]) ? null : p[1];
+
+ assertEquals(rawBaseUri, odr.getRawBaseUri());
+ assertEquals(rawODataPath, odr.getRawODataPath());
+ assertEquals(rawQueryPath, odr.getRawQueryPath());
+ assertEquals(rawRequestUri, odr.getRawRequestUri());
+ assertEquals(rawServiceResolutionUri, odr.getRawServiceResolutionUri());
+ }
+ }
+}
diff --git a/lib/server-core/src/test/java/org/apache/olingo/netty/server/core/ODataNettyImplTest.java b/lib/server-core/src/test/java/org/apache/olingo/netty/server/core/ODataNettyImplTest.java
new file mode 100644
index 000000000..e71ffbd1a
--- /dev/null
+++ b/lib/server-core/src/test/java/org/apache/olingo/netty/server/core/ODataNettyImplTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.netty.server.core;
+
+import static org.junit.Assert.assertNotNull;
+
+import org.apache.olingo.commons.api.format.ContentType;
+import org.apache.olingo.netty.server.api.ODataNetty;
+import org.apache.olingo.server.api.deserializer.DeserializerException;
+import org.apache.olingo.server.api.serializer.SerializerException;
+import org.junit.Test;
+
+public class ODataNettyImplTest {
+
+ private final ODataNetty odata = ODataNetty.newInstance();
+
+ @Test
+ public void serializerSupportedFormats() throws SerializerException {
+ assertNotNull(odata.createSerializer(ContentType.JSON_NO_METADATA));
+ assertNotNull(odata.createSerializer(ContentType.JSON));
+ assertNotNull(odata.createSerializer(ContentType.APPLICATION_JSON));
+ assertNotNull(odata.createSerializer(ContentType.JSON_FULL_METADATA));
+
+ }
+
+ @Test
+ public void deserializerSupportedFormats() throws DeserializerException {
+ assertNotNull(odata.createDeserializer(ContentType.JSON_NO_METADATA));
+ assertNotNull(odata.createDeserializer(ContentType.JSON));
+ assertNotNull(odata.createDeserializer(ContentType.JSON_FULL_METADATA));
+ assertNotNull(odata.createDeserializer(ContentType.APPLICATION_JSON));
+ }
+
+ @Test
+ public void serializerFixedFormat() throws DeserializerException {
+ assertNotNull(odata.createFixedFormatSerializer());
+ }
+
+ @Test
+ public void deserializerFixedFormat() throws DeserializerException {
+ assertNotNull(odata.createFixedFormatDeserializer());
+ }
+}
diff --git a/lib/server-test/src/test/java/org/apache/olingo/netty/server/core/ODataNettyHandlerImplTest.java b/lib/server-test/src/test/java/org/apache/olingo/netty/server/core/ODataNettyHandlerImplTest.java
new file mode 100644
index 000000000..cfe9be4ad
--- /dev/null
+++ b/lib/server-test/src/test/java/org/apache/olingo/netty/server/core/ODataNettyHandlerImplTest.java
@@ -0,0 +1,1259 @@
+/*
+ * 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.netty.server.core;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
+import org.apache.olingo.commons.api.edm.provider.CsdlAbstractEdmProvider;
+import org.apache.olingo.commons.api.edm.provider.CsdlEntitySet;
+import org.apache.olingo.commons.api.edmx.EdmxReference;
+import org.apache.olingo.commons.api.ex.ODataException;
+import org.apache.olingo.commons.api.format.ContentType;
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.commons.api.http.HttpStatusCode;
+import org.apache.olingo.netty.server.api.ODataNetty;
+import org.apache.olingo.netty.server.api.ODataNettyHandler;
+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.ServiceMetadata;
+import org.apache.olingo.server.api.batch.BatchFacade;
+import org.apache.olingo.server.api.processor.ActionComplexCollectionProcessor;
+import org.apache.olingo.server.api.processor.ActionComplexProcessor;
+import org.apache.olingo.server.api.processor.ActionEntityCollectionProcessor;
+import org.apache.olingo.server.api.processor.ActionEntityProcessor;
+import org.apache.olingo.server.api.processor.ActionPrimitiveCollectionProcessor;
+import org.apache.olingo.server.api.processor.ActionPrimitiveProcessor;
+import org.apache.olingo.server.api.processor.ActionVoidProcessor;
+import org.apache.olingo.server.api.processor.BatchProcessor;
+import org.apache.olingo.server.api.processor.ComplexCollectionProcessor;
+import org.apache.olingo.server.api.processor.ComplexProcessor;
+import org.apache.olingo.server.api.processor.CountComplexCollectionProcessor;
+import org.apache.olingo.server.api.processor.CountEntityCollectionProcessor;
+import org.apache.olingo.server.api.processor.CountPrimitiveCollectionProcessor;
+import org.apache.olingo.server.api.processor.EntityCollectionProcessor;
+import org.apache.olingo.server.api.processor.EntityProcessor;
+import org.apache.olingo.server.api.processor.ErrorProcessor;
+import org.apache.olingo.server.api.processor.MediaEntityProcessor;
+import org.apache.olingo.server.api.processor.MetadataProcessor;
+import org.apache.olingo.server.api.processor.PrimitiveCollectionProcessor;
+import org.apache.olingo.server.api.processor.PrimitiveProcessor;
+import org.apache.olingo.server.api.processor.PrimitiveValueProcessor;
+import org.apache.olingo.server.api.processor.Processor;
+import org.apache.olingo.server.api.processor.ReferenceCollectionProcessor;
+import org.apache.olingo.server.api.processor.ReferenceProcessor;
+import org.apache.olingo.server.api.processor.ServiceDocumentProcessor;
+import org.apache.olingo.server.api.uri.UriInfo;
+import org.apache.olingo.server.tecsvc.provider.ContainerProvider;
+import org.apache.olingo.server.tecsvc.provider.EdmTechProvider;
+import org.junit.Test;
+
+import io.netty.buffer.Unpooled;
+import io.netty.handler.codec.http.DefaultFullHttpRequest;
+import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.HttpHeaders;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.HttpResponse;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpVersion;
+
+public class ODataNettyHandlerImplTest {
+
+ private static final String BASE_URI = "http://localhost/odata";
+
+ @Test
+ public void serviceDocumentNonDefault() throws Exception {
+ final ServiceDocumentProcessor processor = mock(ServiceDocumentProcessor.class);
+ doThrow(new ODataApplicationException("msg", 100, Locale.ENGLISH)).when(processor)
+ .readServiceDocument(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class));
+ final ODataResponse response = dispatch(HttpMethod.GET, "/", processor);
+ assertEquals(HttpStatusCode.CONTINUE.getStatusCode(), response.getStatusCode());
+
+ verify(processor).readServiceDocument(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ // We support HEAD now too
+ final ServiceDocumentProcessor processor2 = mock(ServiceDocumentProcessor.class);
+ doThrow(new ODataApplicationException("msg", 100, Locale.ENGLISH)).when(processor2)
+ .readServiceDocument(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class));
+ final ODataResponse response2 = dispatch(HttpMethod.HEAD, "/", processor2);
+ assertEquals(HttpStatusCode.CONTINUE.getStatusCode(), response2.getStatusCode());
+
+ verify(processor2).readServiceDocument(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ dispatchMethodNotAllowed(HttpMethod.POST, "/", processor);
+ dispatchMethodNotAllowed(HttpMethod.PATCH, "/", processor);
+ dispatchMethodNotAllowed(HttpMethod.PUT, "/", processor);
+ dispatchMethodNotAllowed(HttpMethod.DELETE, "/", processor);
+ }
+
+ @Test
+ public void serviceDocumentDefault() throws Exception {
+ final ODataResponse response = dispatch(HttpMethod.GET, "/", null);
+ assertEquals(HttpStatusCode.OK.getStatusCode(), response.getStatusCode());
+
+ String ct = response.getHeader(HttpHeader.CONTENT_TYPE);
+ assertThat(ct, containsString("application/json"));
+ assertThat(ct, containsString("odata.metadata=minimal"));
+
+ assertNotNull(response.getContent());
+ String doc = IOUtils.toString(response.getContent());
+
+ assertThat(doc, containsString("\"@odata.context\":\"$metadata\""));
+ assertThat(doc, containsString("\"value\":"));
+
+ final ODataResponse response2 = dispatch(HttpMethod.HEAD, "/", null);
+ assertEquals(HttpStatusCode.OK.getStatusCode(), response2.getStatusCode());
+ assertNull(response2.getHeader(HttpHeader.CONTENT_TYPE));
+ assertNull(response2.getContent());
+ }
+
+ @Test
+ public void serviceDocumentRedirect() throws Exception {
+ final ODataResponse response = dispatch(HttpMethod.GET, "", null);
+ assertEquals(HttpStatusCode.TEMPORARY_REDIRECT.getStatusCode(), response.getStatusCode());
+ assertEquals(BASE_URI + "/", response.getHeader(HttpHeader.LOCATION));
+
+ final ODataResponse responseHead = dispatch(HttpMethod.HEAD, "", null);
+ assertEquals(HttpStatusCode.TEMPORARY_REDIRECT.getStatusCode(), responseHead.getStatusCode());
+ assertEquals(BASE_URI + "/", responseHead.getHeader(HttpHeader.LOCATION));
+ }
+
+ @Test
+ public void metadataNonDefault() throws Exception {
+ final MetadataProcessor processor = mock(MetadataProcessor.class);
+ doThrow(new ODataApplicationException("msg", 100, Locale.ENGLISH)).when(processor)
+ .readMetadata(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+ final ODataResponse response = dispatch(HttpMethod.GET, "$metadata", processor);
+ assertEquals(HttpStatusCode.CONTINUE.getStatusCode(), response.getStatusCode());
+
+ verify(processor).readMetadata(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ // We support HEAD now too
+ final MetadataProcessor processor2 = mock(MetadataProcessor.class);
+ doThrow(new ODataApplicationException("msg", 100, Locale.ENGLISH)).when(processor2)
+ .readMetadata(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class));
+ final ODataResponse response2 = dispatch(HttpMethod.HEAD, "$metadata", processor2);
+ assertEquals(HttpStatusCode.CONTINUE.getStatusCode(), response2.getStatusCode());
+
+ verify(processor2).readMetadata(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ dispatchMethodNotAllowed(HttpMethod.POST, "$metadata", processor);
+ dispatchMethodNotAllowed(HttpMethod.PATCH, "$metadata", processor);
+ dispatchMethodNotAllowed(HttpMethod.PUT, "$metadata", processor);
+ dispatchMethodNotAllowed(HttpMethod.DELETE, "$metadata", processor);
+ }
+
+ @Test
+ public void metadataDefault() throws Exception {
+ final ODataResponse response = dispatch(HttpMethod.GET, "$metadata", null);
+ assertEquals(HttpStatusCode.OK.getStatusCode(), response.getStatusCode());
+ assertEquals(ContentType.APPLICATION_XML.toContentTypeString(), response.getHeader(HttpHeader.CONTENT_TYPE));
+
+ assertNotNull(response.getContent());
+ assertThat(IOUtils.toString(response.getContent()),
+ containsString(" emptyList());
+
+ ODataRequest request = new ODataRequest();
+ request.setMethod(HttpMethod.GET);
+ request.setRawODataPath("EdmException");
+
+ final ODataResponse response =
+ new ODataNettyHandlerImpl(odata, serviceMetadata).process(request);
+ assertNotNull(response);
+ assertEquals(HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), response.getStatusCode());
+ }
+
+ @Test
+ public void dispatchBatch() throws Exception {
+ final String uri = "$batch";
+ final BatchProcessor processor = mock(BatchProcessor.class);
+
+ dispatch(HttpMethod.POST, uri, null, HttpHeader.CONTENT_TYPE, ContentType.MULTIPART_MIXED.toContentTypeString(),
+ processor);
+ verify(processor).processBatch(any(BatchFacade.class), any(ODataRequest.class), any(ODataResponse.class));
+
+ dispatchMethodNotAllowed(HttpMethod.GET, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.PUT, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.DELETE, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
+ }
+
+ @Test
+ public void dispatchEntitySet() throws Exception {
+ final String uri = "ESAllPrim";
+ final EntityCollectionProcessor processor = mock(EntityCollectionProcessor.class);
+
+ dispatch(HttpMethod.GET, uri, processor);
+ verify(processor).readEntityCollection(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.PUT, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.DELETE, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
+ }
+
+ @Test
+ public void dispatchEntitySetCount() throws Exception {
+ final String uri = "ESAllPrim/$count";
+ final CountEntityCollectionProcessor processor = mock(CountEntityCollectionProcessor.class);
+
+ dispatch(HttpMethod.GET, uri, processor);
+ verify(processor).countEntityCollection(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+
+ dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.PUT, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.DELETE, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
+ }
+
+ @Test
+ public void dispatchCountWithNavigation() throws Exception {
+ final CountEntityCollectionProcessor processor = mock(CountEntityCollectionProcessor.class);
+ String uri = "ESAllPrim(0)/NavPropertyETTwoPrimMany/$count";
+ dispatch(HttpMethod.GET, uri, processor);
+
+ verify(processor).countEntityCollection(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+ dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.PUT, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.DELETE, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
+ }
+
+ @Test
+ public void dispatchFunction() throws Exception {
+ EntityProcessor entityProcessor = mock(EntityProcessor.class);
+ dispatch(HttpMethod.GET, "FICRTETKeyNav()", entityProcessor);
+ verify(entityProcessor).readEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ EntityCollectionProcessor entityCollectionProcessor = mock(EntityCollectionProcessor.class);
+ dispatch(HttpMethod.GET, "FICRTCollESTwoKeyNavParam(ParameterInt16=123)", entityCollectionProcessor);
+ verify(entityCollectionProcessor).readEntityCollection(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ final String entityCountUri = "FICRTCollESTwoKeyNavParam(ParameterInt16=123)/$count";
+ final CountEntityCollectionProcessor entityCountProcessor = mock(CountEntityCollectionProcessor.class);
+ dispatch(HttpMethod.GET, entityCountUri, entityCountProcessor);
+ verify(entityCountProcessor).countEntityCollection(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+ dispatchMethodNotAllowed(HttpMethod.POST, entityCountUri, entityCountProcessor);
+ dispatchMethodNotAllowed(HttpMethod.PATCH, entityCountUri, entityCountProcessor);
+ dispatchMethodNotAllowed(HttpMethod.PUT, entityCountUri, entityCountProcessor);
+ dispatchMethodNotAllowed(HttpMethod.DELETE, entityCountUri, entityCountProcessor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, entityCountUri, entityCountProcessor);
+
+ PrimitiveProcessor primitiveProcessor = mock(PrimitiveProcessor.class);
+ dispatch(HttpMethod.GET, "FICRTString()", primitiveProcessor);
+ verify(primitiveProcessor).readPrimitive(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ // FINRTInt16 is not composable so /$value is not allowed
+ final String valueUri = "FINRTInt16()/$value";
+ final PrimitiveValueProcessor primitiveValueProcessor = mock(PrimitiveValueProcessor.class);
+ dispatchMethodWithError(HttpMethod.GET, valueUri, primitiveValueProcessor, HttpStatusCode.BAD_REQUEST);
+ dispatchMethodWithError(HttpMethod.POST, valueUri, primitiveValueProcessor, HttpStatusCode.BAD_REQUEST);
+ dispatchMethodWithError(HttpMethod.PATCH, valueUri, primitiveValueProcessor, HttpStatusCode.BAD_REQUEST);
+ dispatchMethodWithError(HttpMethod.PUT, valueUri, primitiveValueProcessor, HttpStatusCode.BAD_REQUEST);
+ dispatchMethodWithError(HttpMethod.DELETE, valueUri, primitiveValueProcessor, HttpStatusCode.BAD_REQUEST);
+ dispatchMethodWithError(HttpMethod.HEAD, valueUri, primitiveValueProcessor, HttpStatusCode.BAD_REQUEST);
+
+ final String primitiveCollectionUri = "FICRTCollString()";
+ PrimitiveCollectionProcessor primitiveCollectionProcessor = mock(PrimitiveCollectionProcessor.class);
+ dispatch(HttpMethod.GET, primitiveCollectionUri, primitiveCollectionProcessor);
+ verify(primitiveCollectionProcessor).readPrimitiveCollection(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+ dispatchMethodNotAllowed(HttpMethod.POST, primitiveCollectionUri, primitiveCollectionProcessor);
+ dispatchMethodNotAllowed(HttpMethod.PATCH, primitiveCollectionUri, primitiveCollectionProcessor);
+ dispatchMethodNotAllowed(HttpMethod.PUT, primitiveCollectionUri, primitiveCollectionProcessor);
+ dispatchMethodNotAllowed(HttpMethod.DELETE, primitiveCollectionUri, primitiveCollectionProcessor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, primitiveCollectionUri, primitiveCollectionProcessor);
+
+ final String primitiveCountUri = "FICRTCollString()/$count";
+ final CountPrimitiveCollectionProcessor primitiveCountProcessor = mock(CountPrimitiveCollectionProcessor.class);
+ dispatch(HttpMethod.GET, primitiveCountUri, primitiveCountProcessor);
+ verify(primitiveCountProcessor).countPrimitiveCollection(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+ dispatchMethodNotAllowed(HttpMethod.POST, primitiveCountUri, primitiveCountProcessor);
+ dispatchMethodNotAllowed(HttpMethod.PATCH, primitiveCountUri, primitiveCountProcessor);
+ dispatchMethodNotAllowed(HttpMethod.PUT, primitiveCountUri, primitiveCountProcessor);
+ dispatchMethodNotAllowed(HttpMethod.DELETE, primitiveCountUri, primitiveCountProcessor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, primitiveCountUri, primitiveCountProcessor);
+
+ ComplexProcessor complexProcessor = mock(ComplexProcessor.class);
+ dispatch(HttpMethod.GET, "FICRTCTTwoPrim()", complexProcessor);
+ verify(complexProcessor).readComplex(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ ComplexCollectionProcessor complexCollectionProcessor = mock(ComplexCollectionProcessor.class);
+ dispatch(HttpMethod.GET, "FICRTCollCTTwoPrim()", complexCollectionProcessor);
+ verify(complexCollectionProcessor).readComplexCollection(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ final String complexCountUri = "FICRTCollCTTwoPrim()/$count";
+ final CountComplexCollectionProcessor complexCountProcessor = mock(CountComplexCollectionProcessor.class);
+ dispatch(HttpMethod.GET, complexCountUri, complexCountProcessor);
+ verify(complexCountProcessor).countComplexCollection(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+ dispatchMethodNotAllowed(HttpMethod.POST, complexCountUri, complexCountProcessor);
+ dispatchMethodNotAllowed(HttpMethod.PATCH, complexCountUri, complexCountProcessor);
+ dispatchMethodNotAllowed(HttpMethod.PUT, complexCountUri, complexCountProcessor);
+ dispatchMethodNotAllowed(HttpMethod.DELETE, complexCountUri, complexCountProcessor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, complexCountUri, complexCountProcessor);
+
+ final String mediaUri = "FICRTESMedia(ParameterInt16=1)/$value";
+ final MediaEntityProcessor mediaProcessor = mock(MediaEntityProcessor.class);
+ dispatch(HttpMethod.GET, mediaUri, mediaProcessor);
+ verify(mediaProcessor).readMediaEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+ dispatchMethodNotAllowed(HttpMethod.POST, mediaUri, mediaProcessor);
+ dispatchMethodNotAllowed(HttpMethod.PATCH, mediaUri, mediaProcessor);
+ dispatchMethodNotAllowed(HttpMethod.PUT, mediaUri, mediaProcessor);
+ dispatchMethodNotAllowed(HttpMethod.DELETE, mediaUri, mediaProcessor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, mediaUri, mediaProcessor);
+ }
+
+ @Test
+ public void dispatchAction() throws Exception {
+ final ActionPrimitiveProcessor primitiveProcessor = mock(ActionPrimitiveProcessor.class);
+ dispatch(HttpMethod.POST, ContainerProvider.AIRT_STRING, primitiveProcessor);
+ verify(primitiveProcessor).processActionPrimitive(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class), any(ContentType.class));
+ dispatchMethodNotAllowed(HttpMethod.GET, ContainerProvider.AIRT_STRING, primitiveProcessor);
+ dispatchMethodNotAllowed(HttpMethod.PATCH, ContainerProvider.AIRT_STRING, primitiveProcessor);
+ dispatchMethodNotAllowed(HttpMethod.PUT, ContainerProvider.AIRT_STRING, primitiveProcessor);
+ dispatchMethodNotAllowed(HttpMethod.DELETE, ContainerProvider.AIRT_STRING, primitiveProcessor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, ContainerProvider.AIRT_STRING, primitiveProcessor);
+
+ ActionPrimitiveCollectionProcessor primitiveCollectionProcessor = mock(ActionPrimitiveCollectionProcessor.class);
+ dispatch(HttpMethod.POST, ContainerProvider.AIRT_COLL_STRING_TWO_PARAM, primitiveCollectionProcessor);
+ verify(primitiveCollectionProcessor).processActionPrimitiveCollection(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class), any(ContentType.class));
+
+ ActionComplexProcessor complexProcessor = mock(ActionComplexProcessor.class);
+ dispatch(HttpMethod.POST, ContainerProvider.AIRTCT_TWO_PRIM_PARAM, complexProcessor);
+ verify(complexProcessor).processActionComplex(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class), any(ContentType.class));
+
+ ActionComplexCollectionProcessor complexCollectionProcessor = mock(ActionComplexCollectionProcessor.class);
+ dispatch(HttpMethod.POST, ContainerProvider.AIRT_COLL_CT_TWO_PRIM_PARAM, complexCollectionProcessor);
+ verify(complexCollectionProcessor).processActionComplexCollection(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class), any(ContentType.class));
+
+ ActionEntityProcessor entityProcessor = mock(ActionEntityProcessor.class);
+ dispatch(HttpMethod.POST, ContainerProvider.AIRTET_TWO_KEY_TWO_PRIM_PARAM, entityProcessor);
+ verify(entityProcessor).processActionEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class), any(ContentType.class));
+
+ ActionEntityCollectionProcessor entityCollectionProcessor = mock(ActionEntityCollectionProcessor.class);
+ dispatch(HttpMethod.POST, ContainerProvider.AIRT_COLL_ET_KEY_NAV_PARAM, entityCollectionProcessor);
+ verify(entityCollectionProcessor).processActionEntityCollection(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class), any(ContentType.class));
+
+ ActionEntityProcessor entityProcessorEs = mock(ActionEntityProcessor.class);
+ dispatch(HttpMethod.POST, ContainerProvider.AIRTES_ALL_PRIM_PARAM, entityProcessorEs);
+ verify(entityProcessorEs).processActionEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class), any(ContentType.class));
+
+ ActionEntityCollectionProcessor entityCollectionProcessorEs = mock(ActionEntityCollectionProcessor.class);
+ dispatch(HttpMethod.POST, ContainerProvider.AIRT_COLL_ES_ALL_PRIM_PARAM, entityCollectionProcessorEs);
+ verify(entityCollectionProcessorEs).processActionEntityCollection(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class), any(ContentType.class));
+
+ final ActionVoidProcessor voidProcessor = mock(ActionVoidProcessor.class);
+ dispatch(HttpMethod.POST, ContainerProvider.AIRT, voidProcessor);
+ verify(voidProcessor).processActionVoid(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+ dispatchMethodNotAllowed(HttpMethod.GET, ContainerProvider.AIRT, voidProcessor);
+ dispatchMethodNotAllowed(HttpMethod.PATCH, ContainerProvider.AIRT, voidProcessor);
+ dispatchMethodNotAllowed(HttpMethod.PUT, ContainerProvider.AIRT, voidProcessor);
+ dispatchMethodNotAllowed(HttpMethod.DELETE, ContainerProvider.AIRT, voidProcessor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, ContainerProvider.AIRT, voidProcessor);
+ }
+
+ @Test
+ public void dispatchEntity() throws Exception {
+ final String uri = "ESAllPrim(0)";
+ final EntityProcessor processor = mock(EntityProcessor.class);
+
+ dispatch(HttpMethod.GET, uri, processor);
+ verify(processor).readEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ dispatch(HttpMethod.PATCH, uri, processor);
+ verify(processor).updateEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class),
+ any(ContentType.class));
+
+ dispatch(HttpMethod.PUT, uri, processor);
+ verify(processor, times(2)).updateEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class),
+ any(ContentType.class));
+
+ dispatch(HttpMethod.DELETE, uri, processor);
+ verify(processor).deleteEntity(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+
+ dispatch(HttpMethod.POST, "ESAllPrim", processor);
+ verify(processor).createEntity(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class), any(ContentType.class));
+
+ dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
+ }
+
+
+ @Test
+ public void dispatchSingleton() throws Exception {
+ final String uri = "SI";
+ final EntityProcessor processor = mock(EntityProcessor.class);
+
+ dispatch(HttpMethod.GET, uri, processor);
+ verify(processor).readEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ dispatch(HttpMethod.PATCH, uri, processor);
+ verify(processor).updateEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class),
+ any(ContentType.class));
+
+ dispatch(HttpMethod.PUT, uri, processor);
+ verify(processor, times(2)).updateEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class),
+ any(ContentType.class));
+
+ dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.DELETE, uri, processor);
+ }
+
+ @Test
+ public void dispatchSingletonMedia() throws Exception {
+ final String uri = "SIMedia/$value";
+ final MediaEntityProcessor processor = mock(MediaEntityProcessor.class);
+
+ dispatch(HttpMethod.GET, uri, processor);
+ verify(processor).readMediaEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ dispatch(HttpMethod.PUT, uri, processor);
+ verify(processor).updateMediaEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class),
+ any(ContentType.class));
+
+ dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.DELETE, uri, processor);
+ }
+
+ @Test
+ public void dispatchSingletonNavigation() throws Exception {
+ final String uri = "SINav/NavPropertyETTwoKeyNavOne";
+ final String sigletonNavUri = "ESTwoKeyNav(PropertyInt16=1,PropertyString='1')/NavPropertySINav";
+ final String sigletonManyNavUri = "SINav/NavPropertyETTwoKeyNavMany";
+ final EntityProcessor processor = mock(EntityProcessor.class);
+ final EntityCollectionProcessor collectionProcessor = mock(EntityCollectionProcessor.class);
+
+ dispatch(HttpMethod.GET, sigletonNavUri, processor);
+ verify(processor).readEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ dispatch(HttpMethod.PATCH, sigletonNavUri, processor);
+ verify(processor).updateEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class),
+ any(ContentType.class));
+
+ dispatch(HttpMethod.PUT, sigletonNavUri, processor);
+ verify(processor, times(2)).updateEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class),
+ any(ContentType.class));
+
+ dispatchMethodNotAllowed(HttpMethod.POST, sigletonNavUri, processor);
+ dispatchMethodNotAllowed(HttpMethod.DELETE, sigletonNavUri, processor);
+
+ dispatch(HttpMethod.GET, uri, processor);
+ verify(processor, times(2)).readEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ dispatch(HttpMethod.PATCH, uri, processor);
+ verify(processor, times(3)).updateEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class),
+ any(ContentType.class));
+
+ dispatch(HttpMethod.PUT, uri, processor);
+ verify(processor, times(4)).updateEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class),
+ any(ContentType.class));
+
+ dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+
+ dispatch(HttpMethod.DELETE, uri, processor);
+ verify(processor).deleteEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+
+
+ dispatch(HttpMethod.GET, sigletonManyNavUri, collectionProcessor);
+ verify(collectionProcessor).readEntityCollection(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ dispatchMethodNotAllowed(HttpMethod.PATCH, sigletonManyNavUri, processor);
+
+ dispatchMethodNotAllowed(HttpMethod.PUT, sigletonManyNavUri, processor);
+
+ dispatch(HttpMethod.POST, sigletonManyNavUri, processor);
+ verify(processor).createEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class),
+ any(ContentType.class));
+
+
+ dispatchMethodNotAllowed(HttpMethod.DELETE, sigletonManyNavUri, processor);
+ }
+
+ @Test
+ public void dispatchMedia() throws Exception {
+ final String uri = "ESMedia(1)/$value";
+ final MediaEntityProcessor processor = mock(MediaEntityProcessor.class);
+
+ dispatch(HttpMethod.GET, uri, processor);
+ verify(processor).readMediaEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ dispatch(HttpMethod.POST, "ESMedia", processor);
+ verify(processor).createMediaEntity(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class), any(ContentType.class));
+
+ dispatch(HttpMethod.PUT, uri, processor);
+ verify(processor).updateMediaEntity(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class), any(ContentType.class));
+
+ dispatch(HttpMethod.DELETE, uri, processor);
+ verify(processor).deleteMediaEntity(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+
+ dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
+ }
+
+ @Test
+ public void dispatchValueOnNoMedia() throws Exception {
+ final String uri = "ESAllPrim(1)/$value";
+ final MediaEntityProcessor processor = mock(MediaEntityProcessor.class);
+
+ dispatch(HttpMethod.GET, uri, processor);
+ verifyZeroInteractions(processor);
+
+ dispatch(HttpMethod.POST, uri, processor);
+ verifyZeroInteractions(processor);
+
+ dispatch(HttpMethod.PUT, uri, processor);
+ verifyZeroInteractions(processor);
+
+ dispatch(HttpMethod.DELETE, uri, processor);
+ verifyZeroInteractions(processor);
+
+ dispatch(HttpMethod.HEAD, uri, processor);
+ verifyZeroInteractions(processor);
+ }
+
+ @Test
+ public void dispatchMediaWithNavigation() throws Exception {
+ /*
+ * In Java we decided that any kind of navigation will be accepted. This means that a $value on a media resource
+ * must be dispatched as well
+ */
+ final String uri = "ESKeyNav(1)/NavPropertyETMediaOne/$value";
+ final MediaEntityProcessor processor = mock(MediaEntityProcessor.class);
+
+ dispatch(HttpMethod.GET, uri, processor);
+ verify(processor).readMediaEntity(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ dispatchMethodNotAllowed(HttpMethod.POST, "ESKeyNav(1)/NavPropertyETMediaOne", processor);
+
+ dispatchMethodNotAllowed(HttpMethod.POST, "ESKeyNav(1)/NavPropertyETMediaOne/$value", processor);
+
+ dispatch(HttpMethod.PUT, uri, processor);
+ verify(processor).updateMediaEntity(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class), any(ContentType.class));
+
+ dispatch(HttpMethod.DELETE, uri, processor);
+ verify(processor).deleteMediaEntity(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+
+ dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
+ }
+
+ @Test
+ public void dispatchMediaDeleteIndirect() throws Exception {
+ final MediaEntityProcessor processor = mock(MediaEntityProcessor.class);
+ dispatch(HttpMethod.DELETE, "ESMedia(1)", processor);
+
+ verify(processor).deleteEntity(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+ dispatchMethodNotAllowed(HttpMethod.HEAD, "ESMedia(1)", processor);
+ }
+
+ @Test
+ public void dispatchPrimitiveProperty() throws Exception {
+ final String uri = "ESAllPrim(0)/PropertyString";
+ final PrimitiveProcessor processor = mock(PrimitiveProcessor.class);
+
+ dispatch(HttpMethod.GET, uri, processor);
+ verify(processor).readPrimitive(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ dispatch(HttpMethod.PATCH, uri, processor);
+ verify(processor).updatePrimitive(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class),
+ any(ContentType.class));
+
+ dispatch(HttpMethod.PUT, uri, processor);
+ verify(processor, times(2)).updatePrimitive(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class),
+ any(ContentType.class));
+
+ dispatch(HttpMethod.DELETE, uri, processor);
+ verify(processor).deletePrimitive(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+
+ dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
+ }
+
+ @Test
+ public void dispatchPrimitivePropertyValue() throws Exception {
+ final String uri = "ESAllPrim(0)/PropertyString/$value";
+ final PrimitiveValueProcessor processor = mock(PrimitiveValueProcessor.class);
+
+ dispatch(HttpMethod.GET, uri, processor);
+ verify(processor).readPrimitiveValue(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class));
+
+ dispatch(HttpMethod.PUT, uri, null, HttpHeader.CONTENT_TYPE, ContentType.TEXT_PLAIN.toContentTypeString(),
+ processor);
+ verify(processor).updatePrimitiveValue(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class),
+ any(ContentType.class));
+
+ dispatch(HttpMethod.DELETE, uri, processor);
+ verify(processor).deletePrimitiveValue(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+
+ dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
+ }
+
+ @Test
+ public void dispatchPrimitiveCollectionProperty() throws Exception {
+ final String uri = "ESMixPrimCollComp(7)/CollPropertyString";
+ final PrimitiveCollectionProcessor processor = mock(PrimitiveCollectionProcessor.class);
+
+ dispatch(HttpMethod.GET, uri, processor);
+ verify(processor).readPrimitiveCollection(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ dispatch(HttpMethod.PUT, uri, processor);
+ verify(processor).updatePrimitiveCollection(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class),
+ any(ContentType.class));
+
+ dispatch(HttpMethod.DELETE, uri, processor);
+ verify(processor).deletePrimitiveCollection(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+
+ dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
+ }
+
+ @Test
+ public void dispatchPrimitiveCollectionPropertyCount() throws Exception {
+ final String uri = "ESMixPrimCollComp(7)/CollPropertyString/$count";
+ final CountPrimitiveCollectionProcessor processor = mock(CountPrimitiveCollectionProcessor.class);
+
+ dispatch(HttpMethod.GET, uri, processor);
+ verify(processor).countPrimitiveCollection(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+
+ dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.PUT, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.DELETE, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
+ }
+
+ @Test
+ public void dispatchComplexProperty() throws Exception {
+ final String uri = "ESMixPrimCollComp(7)/PropertyComp";
+ final ComplexProcessor processor = mock(ComplexProcessor.class);
+
+ dispatch(HttpMethod.GET, uri, processor);
+ verify(processor).readComplex(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ dispatch(HttpMethod.PATCH, uri, processor);
+ verify(processor).updateComplex(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class),
+ any(ContentType.class));
+
+ dispatch(HttpMethod.PUT, uri, processor);
+ verify(processor, times(2)).updateComplex(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class),
+ any(ContentType.class));
+
+ dispatch(HttpMethod.DELETE, uri, processor);
+ verify(processor).deleteComplex(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+
+ dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
+ }
+
+ @Test
+ public void dispatchComplexCollectionProperty() throws Exception {
+ final String uri = "ESMixPrimCollComp(7)/CollPropertyComp";
+ final ComplexCollectionProcessor processor = mock(ComplexCollectionProcessor.class);
+
+ dispatch(HttpMethod.GET, uri, processor);
+ verify(processor).readComplexCollection(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
+ dispatch(HttpMethod.PUT, uri, processor);
+ verify(processor).updateComplexCollection(
+ any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class),
+ any(ContentType.class));
+
+ dispatch(HttpMethod.DELETE, uri, processor);
+ verify(processor).deleteComplexCollection(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+
+ dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
+ }
+
+ @Test
+ public void dispatchComplexCollectionPropertyCount() throws Exception {
+ final String uri = "ESMixPrimCollComp(7)/CollPropertyComp/$count";
+ final CountComplexCollectionProcessor processor = mock(CountComplexCollectionProcessor.class);
+
+ dispatch(HttpMethod.GET, uri, processor);
+ verify(processor).countComplexCollection(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+
+ dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.PUT, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.DELETE, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
+ }
+
+ @Test
+ public void dispatchReference() throws Exception {
+ final String uri = "ESAllPrim(0)/NavPropertyETTwoPrimOne/$ref";
+ final String uriMany = "ESAllPrim(0)/NavPropertyETTwoPrimMany/$ref";
+ final String singletonUri = "SINav/NavPropertyETKeyNavOne/$ref";
+ final String singletonUriMany = "SINav/NavPropertyETTwoKeyNavMany/$ref";
+ final String singleUri = "SINav/$ref";
+ final ReferenceProcessor processor = mock(ReferenceProcessor.class);
+
+ dispatch(HttpMethod.GET, uri, processor);
+ verify(processor).readReference(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class));
+
+ dispatch(HttpMethod.PATCH, uri, processor);
+ verify(processor).updateReference(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class));
+
+ dispatch(HttpMethod.PUT, uri, processor);
+ verify(processor, times(2)).updateReference(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class));
+
+ dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+
+ dispatch(HttpMethod.POST, uriMany, processor);
+ verify(processor).createReference(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class));
+
+ dispatch(HttpMethod.DELETE, uriMany, "$id=ESTwoPrim(1)", null, null, processor);
+ verify(processor).deleteReference(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+
+ dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
+
+ //singleton URIs
+
+ dispatch(HttpMethod.GET, singletonUri, processor);
+ verify(processor, times(2)).readReference(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class));
+
+ dispatch(HttpMethod.PATCH, singletonUri, processor);
+ verify(processor, times(3)).updateReference(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class));
+
+ dispatch(HttpMethod.PUT, singletonUri, processor);
+ verify(processor, times(4)).updateReference(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class));
+
+ dispatchMethodNotAllowed(HttpMethod.POST, singletonUri, processor);
+
+ dispatch(HttpMethod.GET, singleUri, processor);
+ verify(processor, times(3)).readReference(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class));
+
+ dispatch(HttpMethod.PATCH, singleUri, processor);
+ verify(processor, times(5)).updateReference(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class));
+
+ dispatch(HttpMethod.PUT, singleUri, processor);
+ verify(processor, times(6)).updateReference(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class));
+
+ dispatchMethodNotAllowed(HttpMethod.POST, singleUri, processor);
+
+ dispatch(HttpMethod.POST, singletonUriMany, processor);
+ verify(processor, times(2)).createReference(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class));
+
+ dispatch(HttpMethod.DELETE, singletonUriMany, "$id=ESTwoPrim(1)", null, null, processor);
+ verify(processor, times(2)).deleteReference(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+
+ dispatchMethodNotAllowed(HttpMethod.HEAD, singletonUriMany, processor);
+ }
+
+ @Test
+ public void dispatchReferenceCollection() throws Exception {
+ final String uri = "ESAllPrim(0)/NavPropertyETTwoPrimMany/$ref";
+ final String singletonUri = "SINav/NavPropertyETTwoKeyNavMany/$ref";
+ final ReferenceCollectionProcessor processor = mock(ReferenceCollectionProcessor.class);
+
+ dispatch(HttpMethod.GET, uri, processor);
+ verify(processor).readReferenceCollection(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class));
+
+ dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.PUT, uri, processor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
+
+ //singleton ref
+ dispatch(HttpMethod.GET, singletonUri, processor);
+ verify(processor, times(2)).readReferenceCollection(any(ODataRequest.class),
+ any(ODataResponse.class), any(UriInfo.class),
+ any(ContentType.class));
+
+ dispatchMethodNotAllowed(HttpMethod.PATCH, singletonUri, processor);
+ dispatchMethodNotAllowed(HttpMethod.PUT, singletonUri, processor);
+ dispatchMethodNotAllowed(HttpMethod.HEAD, singletonUri, processor);
+ }
+
+ @Test
+ public void noRequestContentType() throws Exception {
+ EntityProcessor processor = mock(EntityProcessor.class);
+ final ODataResponse response = dispatch(HttpMethod.POST, "ESAllPrim", null,
+ HttpHeader.CONTENT_TYPE, null, processor);
+ verifyZeroInteractions(processor);
+ assertEquals(HttpStatusCode.BAD_REQUEST.getStatusCode(), response.getStatusCode());
+ }
+
+ @Test
+ public void illegalRequestContentType() throws Exception {
+ EntityProcessor processor = mock(EntityProcessor.class);
+ final ODataResponse response = dispatch(HttpMethod.POST, "ESAllPrim", null,
+ HttpHeader.CONTENT_TYPE, "*/*", processor);
+ verifyZeroInteractions(processor);
+ assertEquals(HttpStatusCode.BAD_REQUEST.getStatusCode(), response.getStatusCode());
+ }
+
+ @Test
+ public void unsupportedRequestContentType() throws Exception {
+ EntityProcessor processor = mock(EntityProcessor.class);
+ ErrorProcessor errorProcessor = mock(ErrorProcessor.class);
+ dispatch(HttpMethod.POST, "ESAllPrim", null, HttpHeader.CONTENT_TYPE, "some/unsupported", errorProcessor);
+ verifyZeroInteractions(processor);
+ verify(errorProcessor).processError(any(ODataRequest.class), any(ODataResponse.class),
+ any(ODataServerError.class),
+ any(ContentType.class));
+ }
+
+ private ODataResponse dispatch(final HttpMethod method, final String path, final String query,
+ final String headerName, final String headerValue, final Processor processor) {
+ ODataRequest request = new ODataRequest();
+ request.setMethod(method);
+ request.setRawBaseUri(BASE_URI);
+ if (path.isEmpty()) {
+ request.setRawRequestUri(BASE_URI);
+ }
+ request.setRawODataPath(path);
+ request.setRawQueryPath(query);
+
+ if (headerName != null) {
+ request.addHeader(headerName, Collections.singletonList(headerValue));
+ }
+
+ if (headerName != HttpHeader.CONTENT_TYPE) {
+ request.addHeader(HttpHeader.CONTENT_TYPE, Collections.singletonList(
+ ContentType.JSON.toContentTypeString()));
+ }
+
+ final ODataNetty odata = ODataNetty.newInstance();
+ final ServiceMetadata metadata = odata.createServiceMetadata(
+ new EdmTechProvider(), Collections. emptyList());
+
+ ODataNettyHandlerImpl handler = new ODataNettyHandlerImpl(odata, metadata);
+
+ if (processor != null) {
+ handler.register(processor);
+ }
+
+ final ODataResponse response = handler.process(request);
+ assertNotNull(response);
+ return response;
+ }
+
+ private ODataResponse dispatch(final HttpMethod method, final String path, final Processor processor) {
+ return dispatch(method, path, null, null, null, processor);
+ }
+
+ private void dispatchMethodNotAllowed(final HttpMethod method, final String path, final Processor processor) {
+ final ODataResponse response = dispatch(method, path, processor);
+ assertEquals(HttpStatusCode.METHOD_NOT_ALLOWED.getStatusCode(), response.getStatusCode());
+ assertNotNull(response.getContent());
+ }
+
+ private void dispatchMethodWithError(final HttpMethod method, final String path, final Processor processor,
+ final HttpStatusCode statusCode) {
+ final ODataResponse response = dispatch(method, path, processor);
+ assertEquals(statusCode.getStatusCode(), response.getStatusCode());
+ assertNotNull(response.getContent());
+ }
+
+ @Test
+ public void testNettyReqResp_GetMethod() {
+ MetadataProcessor processor = mock(MetadataProcessor.class);
+ final ODataNetty odata = ODataNetty.newInstance();
+ final ServiceMetadata metadata = odata.createServiceMetadata(
+ new EdmTechProvider(), Collections. emptyList());
+
+ ODataNettyHandler handler = odata.createNettyHandler(metadata);
+
+ handler.register(processor);
+ DefaultFullHttpRequest nettyRequest = mock(DefaultFullHttpRequest.class);
+ io.netty.handler.codec.http.HttpMethod httpMethod = mock(io.netty.handler.codec.http.HttpMethod.class);
+ when(httpMethod.name()).thenReturn("GET");
+ when(nettyRequest.method()).thenReturn(httpMethod);
+ HttpVersion httpVersion = mock(HttpVersion.class);
+ when(httpVersion.text()).thenReturn("HTTP/1.0");
+ when(nettyRequest.protocolVersion()).thenReturn(httpVersion);
+ when(nettyRequest.uri()).thenReturn("/odata.svc/$metadata");
+ HttpHeaders headers = mock(HttpHeaders.class);
+ headers.add("Accept", "application/atom+xml");
+ Set set = new HashSet();
+ set.add("Accept");
+ when(headers.names()).thenReturn(set);
+ when(nettyRequest.headers()).thenReturn(headers);
+ when(nettyRequest.content()).thenReturn(Unpooled.buffer());
+
+ DefaultFullHttpResponse nettyResponse = mock(DefaultFullHttpResponse.class);
+ when(nettyResponse.status()).thenReturn(HttpResponseStatus.OK);
+ when(nettyResponse.headers()).thenReturn(headers);
+
+ when(nettyResponse.content()).thenReturn(Unpooled.buffer());
+
+ Map requestParams = new HashMap();
+ requestParams.put("contextPath", "/odata.svc");
+ handler.processNettyRequest(nettyRequest, nettyResponse, requestParams);
+
+ assertEquals(HttpStatusCode.OK.getStatusCode(), nettyResponse.status().OK.code());
+ }
+
+ @Test
+ public void testNettyReqResp_POSTMethod() {
+ EntityProcessor processor = mock(EntityProcessor.class);
+ final ODataNetty odata = ODataNetty.newInstance();
+ final ServiceMetadata metadata = odata.createServiceMetadata(
+ new EdmTechProvider(), Collections. emptyList());
+
+ ODataNettyHandler handler = odata.createNettyHandler(metadata);
+
+ handler.register(processor);
+ HttpRequest nettyRequest = mock(DefaultFullHttpRequest.class);
+ io.netty.handler.codec.http.HttpMethod httpMethod = mock(io.netty.handler.codec.http.HttpMethod.class);
+ when(httpMethod.name()).thenReturn("POST");
+ when(nettyRequest.method()).thenReturn(httpMethod);
+ HttpVersion httpVersion = mock(HttpVersion.class);
+ when(httpVersion.text()).thenReturn("HTTP/1.0");
+ when(nettyRequest.protocolVersion()).thenReturn(httpVersion);
+ when(nettyRequest.uri()).thenReturn("/odata.svc/ESAllPrim");
+ HttpHeaders headers = mock(HttpHeaders.class);
+ headers.set("Content-Type", "application/json");
+ Set set = new HashSet();
+ set.add("Content-Type");
+ when(headers.names()).thenReturn(set);
+ List headerValues = new ArrayList();
+ headerValues.add("application/json");
+ when(headers.getAll("Content-Type")).thenReturn(headerValues);
+ when(nettyRequest.headers()).thenReturn(headers);
+ String content = "{\"@odata.context\": \"$metadata#ESAllPrim/$entity\","
+ + "\"PropertyInt16\": 32767,"
+ + "\"PropertyString\": \"First Resource &&&- positive values\","
+ + "\"PropertyBoolean\": true,"
+ + "\"PropertyByte\": 255,"
+ + "\"PropertySByte\": 127,"
+ + "\"PropertyInt32\": 2147483647,"
+ + "\"PropertyInt64\": 9223372036854775807,"
+ + "\"PropertySingle\": 17900000,"
+ + "\"PropertyDouble\": -179000,"
+ + "\"PropertyDecimal\": 34,"
+ + "\"PropertyBinary\": \"ASNFZ4mrze8=\","
+ + "\"PropertyDate\": \"2012-12-03\","
+ + "\"PropertyDateTimeOffset\": \"2012-12-03T07:16:23Z\","
+ + "\"PropertyDuration\": \"PT6S\","
+ + "\"PropertyGuid\": \"01234567-89ab-cdef-0123-456789abcdef\","
+ + "\"PropertyTimeOfDay\": \"03:26:05\"}";
+ byte[] arr = new byte[content.length()];
+ arr = content.getBytes();
+
+ when(((DefaultFullHttpRequest) nettyRequest).content()).thenReturn(Unpooled.copiedBuffer(arr));
+
+ HttpResponse nettyResponse = mock(DefaultFullHttpResponse.class);
+ when(nettyResponse.status()).thenReturn(HttpResponseStatus.CREATED);
+ when(nettyResponse.headers()).thenReturn(headers);
+
+ when(((DefaultFullHttpResponse) nettyResponse).content()).thenReturn(Unpooled.buffer());
+
+ Map requestParams = new HashMap();
+ requestParams.put("contextPath", "/odata.svc");
+ handler.processNettyRequest(nettyRequest, nettyResponse, requestParams);
+
+ assertEquals(HttpStatusCode.CREATED.getStatusCode(), nettyResponse.status().CREATED.code());
+ }
+}