/* * Copyright 2014, The Sporting Exchange Limited * * 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.betfair.cougar.transport.impl.protocol.http; import com.betfair.cougar.core.api.exception.CougarServiceException; import com.betfair.cougar.core.api.exception.CougarValidationException; import com.betfair.cougar.core.api.exception.ServerFaultCode; import com.betfair.cougar.core.api.mediatype.MediaTypeUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.betfair.cougar.util.HeaderUtils; import com.betfair.cougar.util.MessageConstants; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.MediaType; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; public class ContentTypeNormaliserImpl implements ContentTypeNormaliser { private static final Logger LOGGER = LoggerFactory.getLogger(ContentTypeNormaliser.class); public static final String DEFAULT_ENCODING = "utf-8"; private Map<String, MediaType> validContentTypes = new ConcurrentHashMap<String, MediaType>(); private List<MediaType> allContentTypes = Collections.synchronizedList(new ArrayList<MediaType>()); private Set<String> validEncodings = Collections.synchronizedSet(new HashSet<String>()); private String defaultResponseFormat; @Override public void addValidContentTypes(final Set<String> vct, MediaType normalisedContentType) { for (String ct : vct) { if (this.validContentTypes.containsKey(ct)) { // this content type is already registered. Ensure it's mapped to the same preferred type MediaType mType = this.validContentTypes.get(ct); if (!mType.equals(normalisedContentType)) { throw new IllegalArgumentException("Content type " + ct + " is already registered with normalised type " + mType + ", cannot re-register to " + normalisedContentType); } } else { this.validContentTypes.put(ct, normalisedContentType); } } allContentTypes.addAll(MediaTypeUtils.getMediaTypes(new ArrayList<String>(vct).toArray(new String[vct.size()]))); } @Override public void addValidEncodings(final Set<String> validEncodings) { this.validEncodings.addAll(validEncodings); } @Override public MediaType getNormalisedResponseMediaType(HttpServletRequest request) { // Negotiate Response format MediaType responseMediaType; String responseFormat = getResponseFormat(request); try { List<MediaType> acceptMT = MediaTypeUtils.parseMediaTypes(responseFormat); responseMediaType = MediaTypeUtils.getResponseMediaType(allContentTypes, acceptMT); if (responseMediaType == null) { throw new CougarValidationException(ServerFaultCode.AcceptTypeNotValid, "Could not agree a response media type"); } else if (responseMediaType.isWildcardType() || responseMediaType.isWildcardSubtype()) { throw new CougarServiceException(ServerFaultCode.ResponseContentTypeNotValid, "Service configuration error - response media type must not be a wildcard - " + responseMediaType); } } catch (IllegalArgumentException e) { throw new CougarValidationException(ServerFaultCode.MediaTypeParseFailure, "Unable to parse supplied media types (" + responseFormat + ")",e); } return responseMediaType; } private String getResponseFormat(HttpServletRequest request) { String paramFormat = request.getParameter(MessageConstants.FORMAT_PARAMETER); if (paramFormat == null || paramFormat.length() == 0) { paramFormat = HeaderUtils.getAccept(request); if (paramFormat == null || paramFormat.length() == 0) { // Need to default to something paramFormat = defaultResponseFormat; } } else if (paramFormat.equalsIgnoreCase("xml")) { paramFormat = MediaType.APPLICATION_XML; } else if (paramFormat.equalsIgnoreCase("json")) { paramFormat = MediaType.APPLICATION_JSON; } else if (paramFormat.equalsIgnoreCase("bin")) { paramFormat = MediaType.APPLICATION_OCTET_STREAM; } else { throw new CougarValidationException(ServerFaultCode.MediaTypeParseFailure, "Invalid alt param for media type - " + paramFormat); } return paramFormat; } @Override public MediaType getNormalisedRequestMediaType(HttpServletRequest request) { String contentType = request.getContentType(); if (request.getMethod().equals("POST")) { if (contentType == null) { throw new CougarValidationException(ServerFaultCode.ContentTypeNotValid, "Input content type was not specified for deserialisable response"); } MediaType requestMT; try { requestMT = MediaType.valueOf(contentType); } catch (Exception e) { throw new CougarValidationException(ServerFaultCode.MediaTypeParseFailure, "Input content type cannot be parsed: " + contentType,e); } if (requestMT.isWildcardType() || requestMT.isWildcardSubtype()) { throw new CougarValidationException(ServerFaultCode.InvalidInputMediaType, "Input content type may not be wildcard: " + requestMT); } if (!MediaTypeUtils.isValid(allContentTypes, requestMT)) { throw new CougarValidationException(ServerFaultCode.ContentTypeNotValid, "Input content type is not valid: " + requestMT); } String candidateContentType = requestMT.getType() + "/" + requestMT.getSubtype(); MediaType normalizedMediaType = validContentTypes.get(candidateContentType); if (normalizedMediaType == null) { throw new CougarValidationException(ServerFaultCode.FrameworkError, "Input content type " + contentType + " failed to find a normalized type using key " + candidateContentType); } return normalizedMediaType; } return null; } public void setDefaultResponseFormat(String defaultResponseFormat) { this.defaultResponseFormat = defaultResponseFormat; } @Override public String getNormalisedEncoding(HttpServletRequest request) { return getEncoding(request.getContentType()); } private static String CHARSET = "charset="; //EG: Content-Type: text/html; charset=utf-8 private String getEncoding(final String contentType) { if (contentType == null || !contentType.contains(CHARSET)) { return DEFAULT_ENCODING; } String encoding = null; try { encoding = contentType.substring(contentType.indexOf(CHARSET) + CHARSET.length()); encoding = encoding.toLowerCase().replaceAll("\"", ""); } catch (Exception e) { //Extraction from the string failed. } if (!validEncodings.contains(encoding)) { LOGGER.warn("Invalid Encoding '{}' - using default - '{}'", encoding, DEFAULT_ENCODING); encoding = DEFAULT_ENCODING; } return encoding; } }