blob: b6ebc6ba5b3d65df8650a8595b37d2a25034234a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.core;
import java.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.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.server.api.OData;
import org.apache.olingo.server.api.ODataContent;
import org.apache.olingo.server.api.ODataHttpHandler;
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.OlingoExtension;
import org.apache.olingo.server.api.ServiceMetadata;
import org.apache.olingo.server.api.debug.DebugSupport;
import org.apache.olingo.server.api.deserializer.DeserializerException;
import org.apache.olingo.server.api.etag.CustomETagSupport;
import org.apache.olingo.server.api.processor.Processor;
import org.apache.olingo.server.api.serializer.CustomContentTypeSupport;
import org.apache.olingo.server.core.debug.ServerCoreDebugger;
public class ODataHttpHandlerImpl implements ODataHttpHandler {
public static final int COPY_BUFFER_SIZE = 8192;
private final ODataHandlerImpl handler;
private final ServerCoreDebugger debugger;
private int split = 0;
public ODataHttpHandlerImpl(final OData odata, final ServiceMetadata serviceMetadata) {
debugger = new ServerCoreDebugger(odata);
handler = new ODataHandlerImpl(odata, serviceMetadata, debugger);
}
@Override
public ODataResponse process(ODataRequest request) {
return handler.process(request);
}
@Override
public void process(final HttpServletRequest request, final HttpServletResponse response) {
ODataRequest odRequest = new ODataRequest();
Exception exception = null;
ODataResponse odResponse;
debugger.resolveDebugMode(request);
final int processMethodHandle = debugger.startRuntimeMeasurement("ODataHttpHandlerImpl", "process");
try {
fillODataRequest(odRequest, request, split);
odResponse = process(odRequest);
// ALL future methods after process must not throw exceptions!
} catch (Exception e) {
exception = e;
odResponse = handleException(odRequest, e);
}
debugger.stopRuntimeMeasurement(processMethodHandle);
if (debugger.isDebugMode()) {
Map<String, String> serverEnvironmentVariables = createEnvironmentVariablesMap(request);
if (exception == null) {
// This is to ensure that we have access to the thrown OData Exception
exception = handler.getLastThrownException();
}
odResponse =
debugger.createDebugResponse(odRequest, odResponse, exception, handler.getUriInfo(),
serverEnvironmentVariables);
}
convertToHttp(response, odResponse);
}
private Map<String, String> createEnvironmentVariablesMap(final HttpServletRequest request) {
Map<String, String> environment = new LinkedHashMap<String, String>();
environment.put("authType", request.getAuthType());
environment.put("localAddr", request.getLocalAddr());
environment.put("localName", request.getLocalName());
environment.put("localPort", getIntAsString(request.getLocalPort()));
environment.put("pathInfo", request.getPathInfo());
environment.put("pathTranslated", request.getPathTranslated());
environment.put("remoteAddr", request.getRemoteAddr());
environment.put("remoteHost", request.getRemoteHost());
environment.put("remotePort", getIntAsString(request.getRemotePort()));
environment.put("remoteUser", request.getRemoteUser());
environment.put("scheme", request.getScheme());
environment.put("serverName", request.getServerName());
environment.put("serverPort", getIntAsString(request.getServerPort()));
environment.put("servletPath", request.getServletPath());
return environment;
}
private String getIntAsString(final int number) {
return number == 0 ? "unknown" : Integer.toString(number);
}
@Override
public void setSplit(final int split) {
this.split = split;
}
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;
}
static void convertToHttp(final HttpServletResponse response, final ODataResponse odResponse) {
response.setStatus(odResponse.getStatusCode());
for (Entry<String, List<String>> entry : odResponse.getAllHeaders().entrySet()) {
for (String headerValue : entry.getValue()) {
response.addHeader(entry.getKey(), headerValue);
}
}
if (odResponse.getContent() != null) {
copyContent(odResponse.getContent(), response);
} else if (odResponse.getODataContent() != null) {
writeContent(odResponse, response);
}
}
static void writeContent(final ODataResponse odataResponse, final HttpServletResponse servletResponse) {
try {
ODataContent res = odataResponse.getODataContent();
res.write(Channels.newChannel(servletResponse.getOutputStream()));
} catch (IOException e) {
throw new ODataRuntimeException("Error on reading request content", e);
}
}
static void copyContent(final InputStream inputStream, final HttpServletResponse servletResponse) {
copyContent(Channels.newChannel(inputStream), servletResponse);
}
static void copyContent(final ReadableByteChannel input, final HttpServletResponse servletResponse) {
WritableByteChannel output = null;
try {
ByteBuffer inBuffer = ByteBuffer.allocate(COPY_BUFFER_SIZE);
output = Channels.newChannel(servletResponse.getOutputStream());
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
}
}
}
private ODataRequest fillODataRequest(final ODataRequest odRequest, final HttpServletRequest httpRequest,
final int split) throws ODataLibraryException {
final int requestHandle = debugger.startRuntimeMeasurement("ODataHttpHandlerImpl", "fillODataRequest");
try {
odRequest.setBody(httpRequest.getInputStream());
odRequest.setProtocol(httpRequest.getProtocol());
odRequest.setMethod(extractMethod(httpRequest));
int innerHandle = debugger.startRuntimeMeasurement("ODataHttpHandlerImpl", "copyHeaders");
copyHeaders(odRequest, httpRequest);
debugger.stopRuntimeMeasurement(innerHandle);
innerHandle = debugger.startRuntimeMeasurement("ODataHttpHandlerImpl", "fillUriInformation");
fillUriInformation(odRequest, httpRequest, split);
debugger.stopRuntimeMeasurement(innerHandle);
return odRequest;
} catch (final IOException e) {
throw new DeserializerException("An I/O exception occurred.", e,
DeserializerException.MessageKeys.IO_EXCEPTION);
} finally {
debugger.stopRuntimeMeasurement(requestHandle);
}
}
static HttpMethod extractMethod(final HttpServletRequest httpRequest) throws ODataLibraryException {
final HttpMethod httpRequestMethod;
try {
httpRequestMethod = HttpMethod.valueOf(httpRequest.getMethod());
} catch (IllegalArgumentException e) {
throw new ODataHandlerException("HTTP method not allowed" + httpRequest.getMethod(), e,
ODataHandlerException.MessageKeys.HTTP_METHOD_NOT_ALLOWED, httpRequest.getMethod());
}
try {
if (httpRequestMethod == HttpMethod.POST) {
String xHttpMethod = httpRequest.getHeader(HttpHeader.X_HTTP_METHOD);
String xHttpMethodOverride = httpRequest.getHeader(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.getMethod(), e,
ODataHandlerException.MessageKeys.INVALID_HTTP_METHOD, httpRequest.getMethod());
}
}
static void fillUriInformation(final ODataRequest odRequest, final HttpServletRequest httpRequest, final int split) {
String rawRequestUri = httpRequest.getRequestURL().toString();
String rawODataPath;
if (!"".equals(httpRequest.getServletPath())) {
int beginIndex = rawRequestUri.indexOf(httpRequest.getServletPath()) + httpRequest.getServletPath().length();
rawODataPath = rawRequestUri.substring(beginIndex);
} else if (!"".equals(httpRequest.getContextPath())) {
int beginIndex = rawRequestUri.indexOf(httpRequest.getContextPath()) + httpRequest.getContextPath().length();
rawODataPath = rawRequestUri.substring(beginIndex);
} else {
rawODataPath = httpRequest.getRequestURI();
}
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());
odRequest.setRawQueryPath(httpRequest.getQueryString());
odRequest.setRawRequestUri(rawRequestUri
+ (httpRequest.getQueryString() == null ? "" : "?" + httpRequest.getQueryString()));
odRequest.setRawODataPath(rawODataPath);
odRequest.setRawBaseUri(rawBaseUri);
odRequest.setRawServiceResolutionUri(rawServiceResolutionUri);
}
static void copyHeaders(ODataRequest odRequest, final HttpServletRequest req) {
for (final Enumeration<?> headerNames = req.getHeaderNames(); headerNames.hasMoreElements();) {
final String headerName = (String) headerNames.nextElement();
@SuppressWarnings("unchecked")
// getHeaders() says it returns an Enumeration of String.
final List<String> headerValues = Collections.list(req.getHeaders(headerName));
odRequest.addHeader(headerName, headerValues);
}
}
@Override
public void register(final Processor processor) {
handler.register(processor);
}
@Override
public void register(OlingoExtension extension) {
handler.register(extension);
}
@Override
public void register(final CustomContentTypeSupport customContentTypeSupport) {
handler.register(customContentTypeSupport);
}
@Override
public void register(final CustomETagSupport customConcurrencyControlSupport) {
handler.register(customConcurrencyControlSupport);
}
@Override
public void register(final DebugSupport debugSupport) {
debugger.setDebugSupportProcessor(debugSupport);
}
}