/******************************************************************************* * 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; } }