blob: dab0efa813e645ffe3278ff63801561fafb74427 [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.odata2.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.olingo.odata2.api.exception.ODataBadRequestException;
import org.apache.olingo.odata2.api.exception.ODataException;
import org.apache.olingo.odata2.api.exception.ODataNotAcceptableException;
import org.apache.olingo.odata2.api.processor.ODataRequest;
import org.apache.olingo.odata2.core.commons.ContentType;
import org.apache.olingo.odata2.core.uri.UriInfoImpl;
import org.apache.olingo.odata2.core.uri.UriType;
/**
* Handles content negotiation with handling of OData special cases.
*/
public class ContentNegotiator {
private static final String URI_INFO_FORMAT_JSON = "json";
private static final String URI_INFO_FORMAT_ATOM = "atom";
private static final String URI_INFO_FORMAT_XML = "xml";
static final String DEFAULT_CHARSET = "utf-8";
/**
* Do the content negotiation for <code>accept header value</code> based on
* requested content type (in HTTP accept header from {@link ODataRequest})
* in combination with uri information from {@link org.apache.olingo.odata2.api.uri.UriInfo} and from given supported
* content types (via
* <code>supportedContentTypes</code>).
*
* @param request specific request
* @param uriInfo specific uri information
* @param supportedContentTypes list of supported content types
* @return best fitting content type or <code>NULL</code> if content type is not set and for given
* {@link org.apache.olingo.odata2.api.uri.UriInfo} is
* ignored
* @throws ODataException if no supported content type was found
* @throws IllegalArgumentException if one of the input parameter is <code>NULL</code>
*/
public ContentType doContentNegotiation(final ODataRequest odataRequest, final UriInfoImpl uriInfo,
final List<String> supportedContentTypes) throws ODataException {
validateNotNull(odataRequest, uriInfo, supportedContentTypes);
if (uriInfo.isCount()) {
return ContentType.TEXT_PLAIN_CS_UTF_8;
} else if (uriInfo.isValue()) {
if (uriInfo.getUriType() == UriType.URI5 || uriInfo.getUriType() == UriType.URI4) {
return ContentType.TEXT_PLAIN_CS_UTF_8;
}
return doContentNegotiationForAcceptHeader(Arrays.asList("*/*"), ContentType.create(supportedContentTypes));
}
if (uriInfo.getFormat() == null) {
return doContentNegotiationForAcceptHeader(odataRequest.getAcceptHeaders(), ContentType
.create(supportedContentTypes));
} else {
return doContentNegotiationForFormat(uriInfo, ContentType.createAsCustom(supportedContentTypes));
}
}
private void validateNotNull(final ODataRequest odataRequest, final UriInfoImpl uriInfo,
final List<String> supportedContentTypes) {
if (uriInfo == null) {
throw new IllegalArgumentException("Parameter uriInfo MUST NOT be null.");
}
if (odataRequest == null) {
throw new IllegalArgumentException("Parameter odataRequest MUST NOT be null.");
}
if (supportedContentTypes == null) {
throw new IllegalArgumentException("Parameter supportedContentTypes MUST NOT be null.");
}
}
private ContentType doContentNegotiationForFormat(final UriInfoImpl uriInfo,
final List<ContentType> supportedContentTypes) throws ODataException {
validateFormatQuery(uriInfo);
ContentType formatContentType = mapFormat(uriInfo);
formatContentType = ensureCharset(formatContentType);
for (final ContentType contentType : supportedContentTypes) {
if (contentType.equals(formatContentType)) {
return formatContentType;
}
}
throw new ODataNotAcceptableException(ODataNotAcceptableException.NOT_SUPPORTED_CONTENT_TYPE.addContent(uriInfo
.getFormat()));
}
/**
* Validates that <code>dollar format query/syntax</code> is correct for further processing.
* If some validation error occurs an exception is thrown.
*
* @param uriInfo
* @throws ODataBadRequestException
*/
private void validateFormatQuery(final UriInfoImpl uriInfo) throws ODataBadRequestException {
if (uriInfo.isValue()) {
throw new ODataBadRequestException(ODataBadRequestException.INVALID_SYNTAX);
}
}
private ContentType mapFormat(final UriInfoImpl uriInfo) {
final String format = uriInfo.getFormat();
if (URI_INFO_FORMAT_XML.equals(format)) {
return ContentType.APPLICATION_XML;
} else if (URI_INFO_FORMAT_ATOM.equals(format)) {
if (uriInfo.getUriType() == UriType.URI0) {
// special handling for serviceDocument uris (UriType.URI0)
return ContentType.APPLICATION_ATOM_SVC;
} else if (uriInfo.getUriType() == UriType.URI1) {
return ContentType.APPLICATION_ATOM_XML_FEED;
} else if (uriInfo.getUriType() == UriType.URI2 || uriInfo.getUriType() == UriType.URI10) {
return ContentType.APPLICATION_ATOM_XML_ENTRY;
}
} else if (URI_INFO_FORMAT_JSON.equals(format)) {
return ContentType.APPLICATION_JSON;
}
return ContentType.createAsCustom(format);
}
private ContentType doContentNegotiationForAcceptHeader(final List<String> acceptHeaderContentTypes,
final List<ContentType> supportedContentTypes) throws ODataException {
return contentNegotiation(extractAcceptHeaders(acceptHeaderContentTypes), supportedContentTypes);
}
private List<ContentType> extractAcceptHeaders(final List<String> acceptHeaderValues)
throws ODataBadRequestException {
final List<ContentType> mediaTypes = new ArrayList<ContentType>();
if (acceptHeaderValues != null) {
for (final String mediaType : acceptHeaderValues) {
try {
mediaTypes.add(ContentType.create(mediaType.toString()));
} catch (IllegalArgumentException e) {
throw new ODataBadRequestException(ODataBadRequestException.INVALID_HEADER.addContent("Accept")
.addContent(mediaType.toString()), e);
}
}
}
return mediaTypes;
}
ContentType contentNegotiation(final List<ContentType> acceptedContentTypes,
final List<ContentType> supportedContentTypes) throws ODataException {
final Set<ContentType> setSupported = new HashSet<ContentType>(supportedContentTypes);
if (acceptedContentTypes.isEmpty()) {
if (!setSupported.isEmpty()) {
return supportedContentTypes.get(0);
}
} else {
for (ContentType contentType : acceptedContentTypes) {
contentType = ensureCharset(contentType);
final ContentType match = contentType.match(supportedContentTypes);
if (match != null) {
return match;
}
}
}
throw new ODataNotAcceptableException(ODataNotAcceptableException.NOT_SUPPORTED_ACCEPT_HEADER
.addContent(acceptedContentTypes.toString()));
}
private ContentType ensureCharset(final ContentType contentType) {
if (ContentType.APPLICATION_ATOM_XML.isCompatible(contentType)
|| ContentType.APPLICATION_ATOM_SVC.isCompatible(contentType)
|| ContentType.APPLICATION_XML.isCompatible(contentType)) {
return contentType.receiveWithCharsetParameter(DEFAULT_CHARSET);
}
return contentType;
}
}