[OLINGO-1201] Enhancements to run better with Netty
This commit is contained in:
parent
5e21bb2ba5
commit
03ab69c498
|
@ -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);
|
||||||
|
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Processes a NettyRequest as an OData request.</p>
|
||||||
|
* <p>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.</p>
|
||||||
|
* @param request - must be a HTTP OData request
|
||||||
|
* @param response - HTTP OData response
|
||||||
|
*/
|
||||||
|
void processNettyRequest(HttpRequest request, HttpResponse response, Map<String, String> requestParameters);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Registers additional custom processor implementations for handling OData requests.</p>
|
||||||
|
* <p>If request processing requires a processor that is not registered then a
|
||||||
|
* "not implemented" exception will happen.</p>
|
||||||
|
*/
|
||||||
|
void register(Processor processor);
|
||||||
|
|
||||||
|
}
|
|
@ -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<String, List<String>> 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<String> headers = req.headers().names();
|
||||||
|
Iterator<String> headerNames = headers.iterator();
|
||||||
|
while (headerNames.hasNext()) {
|
||||||
|
final String headerName = headerNames.next();
|
||||||
|
final List<String> headerValues = req.headers().getAll(headerName);
|
||||||
|
odRequest.addHeader(headerName, headerValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Override
|
||||||
|
public void processNettyRequest(HttpRequest request, HttpResponse response,
|
||||||
|
Map<String, String> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String> 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<EdmxReference> references) {
|
||||||
|
return odata.createServiceMetadata(edmProvider, references);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ServiceMetadata createServiceMetadata(CsdlEdmProvider edmProvider, List<EdmxReference> 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<String> 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<String> versions)
|
||||||
|
throws SerializerException {
|
||||||
|
return odata.createEdmDeltaSerializer(contentType, versions);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue