| /* |
| * 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) { |
| try (WritableByteChannel output = Channels.newChannel(new ByteBufOutputStream(((HttpContent)response).content()))){ |
| ByteBuffer inBuffer = ByteBuffer.allocate(COPY_BUFFER_SIZE); |
| while (input.read(inBuffer) > 0) { |
| inBuffer.flip(); |
| output.write(inBuffer); |
| inBuffer.clear(); |
| } |
| closeStream(output); |
| } catch (IOException e) { |
| throw new ODataRuntimeException("Error on reading request content", e); |
| } finally { |
| closeStream(input); |
| } |
| } |
| |
| 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); |
| } |
| } |