/*******************************************************************************
* Copyright 2013 SAP AG
*
* Licensed 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 com.sap.core.odata.core.rest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import com.sap.core.odata.api.commons.HttpHeaders;
import com.sap.core.odata.api.exception.ODataBadRequestException;
import com.sap.core.odata.api.exception.ODataException;
import com.sap.core.odata.api.exception.ODataNotFoundException;
import com.sap.core.odata.api.processor.ODataResponse;
import com.sap.core.odata.api.uri.PathSegment;
import com.sap.core.odata.core.ODataPathSegmentImpl;
import com.sap.core.odata.core.PathInfoImpl;
import com.sap.core.odata.core.commons.ContentType;
import com.sap.core.odata.core.commons.Decoder;
/**
* @author SAP AG
*/
public class RestUtil {
public static Response convertResponse(final ODataResponse odataResponse) {
try {
ResponseBuilder responseBuilder = Response.noContent().status(odataResponse.getStatus().getStatusCode()).entity(odataResponse.getEntity());
for (final String name : odataResponse.getHeaderNames()) {
responseBuilder = responseBuilder.header(name, odataResponse.getHeader(name));
}
return responseBuilder.build();
} catch (RuntimeException e) {
if (odataResponse != null) {
try {
odataResponse.close();
} catch (IOException inner) {
// if close throw an exception we ignore these and re-theow our exception
throw e;
}
}
throw e;
}
}
public static ContentType extractRequestContentType(final SubLocatorParameter param) throws ODataBadRequestException {
final String contentType = param.getHttpHeaders().getHeaderString(HttpHeaders.CONTENT_TYPE);
if (contentType == null || contentType.isEmpty()) {
// RFC 2616, 7.2.1:
// "Any HTTP/1.1 message containing an entity-body SHOULD include a
// Content-Type header field defining the media type of that body. [...]
// If the media type remains unknown, the recipient SHOULD treat it
// as type "application/octet-stream"."
return ContentType.APPLICATION_OCTET_STREAM;
} else if (ContentType.isParseable(contentType)) {
return ContentType.create(contentType);
} else {
throw new ODataBadRequestException(ODataBadRequestException.INVALID_HEADER.addContent(HttpHeaders.CONTENT_TYPE, contentType));
}
}
/**
* Extracts the request content from the servlet as input stream.
* @param param initialization parameters
* @return the request content as input stream
* @throws ODataException
*/
public static ServletInputStream extractRequestContent(final SubLocatorParameter param) throws ODataException {
try {
return param.getServletRequest().getInputStream();
} catch (final IOException e) {
throw new ODataException("Error getting request content as ServletInputStream.", e);
}
}
public static <T> InputStream contentAsStream(final T content) throws ODataException {
if (content == null) {
throw new ODataBadRequestException(ODataBadRequestException.COMMON);
}
InputStream inputStream;
if (content instanceof InputStream) {
inputStream = (InputStream) content;
} else if (content instanceof String) {
try {
inputStream = new ByteArrayInputStream(((String) content).getBytes("UTF-8"));
} catch (final UnsupportedEncodingException e) {
throw new ODataBadRequestException(ODataBadRequestException.COMMON, e);
}
} else {
throw new ODataBadRequestException(ODataBadRequestException.COMMON);
}
return inputStream;
}
public static List<String> extractAcceptHeaders(final SubLocatorParameter param) throws ODataBadRequestException {
// first validate all accept header content types are 'parseable' and valif from our point of view
List<String> acceptHeaders = param.getHttpHeaders().getRequestHeader(HttpHeaders.ACCEPT);
for (String acceptHeader : acceptHeaders) {
String[] contentTypes = acceptHeader.split(",");
for (String contentType : contentTypes) {
if (!ContentType.isParseable(contentType.trim())) {
throw new ODataBadRequestException(ODataBadRequestException.INVALID_HEADER.addContent(HttpHeaders.ACCEPT, acceptHeader));
}
}
}
// then get sorted list of acceptable media type from jax-rs
final List<String> mediaTypes = new ArrayList<String>();
final List<MediaType> acceptableMediaTypes = param.getHttpHeaders().getAcceptableMediaTypes();
for (final MediaType mediaType : acceptableMediaTypes) {
mediaTypes.add(mediaType.toString());
}
return mediaTypes;
}
public static Map<String, String> extractRequestHeaders(final javax.ws.rs.core.HttpHeaders httpHeaders) {
final MultivaluedMap<String, String> headers = httpHeaders.getRequestHeaders();
Map<String, String> headerMap = new HashMap<String, String>();
for (final String key : headers.keySet()) {
List<String> header = httpHeaders.getRequestHeader(key);
if (header != null && !header.isEmpty()) {
/*
* consider first header value only
* avoid using jax-rs 2.0 (getHeaderString())
*/
String value = header.get(0);
if (value != null && !"".equals(value)) {
headerMap.put(key, value);
}
}
}
return headerMap;
}
public static PathInfoImpl buildODataPathInfo(final SubLocatorParameter param) throws ODataException {
final UriInfo uriInfo = param.getUriInfo();
PathInfoImpl pathInfo = splitPath(param);
pathInfo.setServiceRoot(buildBaseUri(param.getServletRequest(), uriInfo, pathInfo.getPrecedingSegments()));
pathInfo.setRequestUri(uriInfo.getRequestUri());
return pathInfo;
}
private static PathInfoImpl splitPath(final SubLocatorParameter param) throws ODataException {
PathInfoImpl pathInfo = new PathInfoImpl();
List<javax.ws.rs.core.PathSegment> precedingPathSegments;
List<javax.ws.rs.core.PathSegment> pathSegments;
if (param.getPathSplit() == 0) {
precedingPathSegments = Collections.emptyList();
pathSegments = param.getPathSegments();
} else {
if (param.getPathSegments().size() < param.getPathSplit()) {
throw new ODataBadRequestException(ODataBadRequestException.URLTOOSHORT);
}
precedingPathSegments = param.getPathSegments().subList(0, param.getPathSplit());
final int pathSegmentCount = param.getPathSegments().size();
pathSegments = param.getPathSegments().subList(param.getPathSplit(), pathSegmentCount);
}
// Percent-decode only the preceding path segments.
// The OData path segments are decoded during URI parsing.
pathInfo.setPrecedingPathSegment(convertPathSegmentList(precedingPathSegments));
List<PathSegment> odataSegments = new ArrayList<PathSegment>();
for (final javax.ws.rs.core.PathSegment segment : pathSegments) {
if (segment.getMatrixParameters() == null || segment.getMatrixParameters().isEmpty()) {
odataSegments.add(new ODataPathSegmentImpl(segment.getPath(), null));
} else {
// post condition: we do not allow matrix parameters in OData path segments
throw new ODataNotFoundException(ODataNotFoundException.MATRIX.addContent(segment.getMatrixParameters().keySet(), segment.getPath()));
}
}
pathInfo.setODataPathSegment(odataSegments);
return pathInfo;
}
private static URI buildBaseUri(final HttpServletRequest request, final javax.ws.rs.core.UriInfo uriInfo, final List<PathSegment> precedingPathSegments) throws ODataException {
try {
UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
for (final PathSegment ps : precedingPathSegments) {
uriBuilder = uriBuilder.path(ps.getPath());
for (final String key : ps.getMatrixParameters().keySet()) {
final Object[] v = ps.getMatrixParameters().get(key).toArray();
uriBuilder = uriBuilder.matrixParam(key, v);
}
}
/*
* workaround because of host name is cached by uriInfo
*/
uriBuilder.host(request.getServerName());
String uriString = uriBuilder.build().toString();
if (!uriString.endsWith("/")) {
uriString = uriString + "/";
}
return new URI(uriString);
} catch (final URISyntaxException e) {
throw new ODataException(e);
}
}
private static List<PathSegment> convertPathSegmentList(final List<javax.ws.rs.core.PathSegment> pathSegments) {
ArrayList<PathSegment> converted = new ArrayList<PathSegment>();
for (final javax.ws.rs.core.PathSegment pathSegment : pathSegments) {
final PathSegment segment = new ODataPathSegmentImpl(Decoder.decode(pathSegment.getPath()), pathSegment.getMatrixParameters());
converted.add(segment);
}
return converted;
}
public static Map<String, String> convertToSinglevaluedMap(final MultivaluedMap<String, String> multi) {
final Map<String, String> single = new HashMap<String, String>();
for (final String key : multi.keySet()) {
final String value = multi.getFirst(key);
single.put(key, value);
}
return single;
}
}