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