/******************************************************************************* * Copyright (c) 2012-2016 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.everrest.core.impl.header; import org.everrest.core.util.MediaTypeComparator; import javax.ws.rs.Consumes; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.ext.ContextResolver; import javax.ws.rs.ext.MessageBodyReader; import javax.ws.rs.ext.MessageBodyWriter; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.regex.Matcher; import java.util.regex.Pattern; import static java.util.stream.Collectors.toList; import static javax.ws.rs.core.MediaType.MEDIA_TYPE_WILDCARD; public final class MediaTypeHelper { /** List which contains default media type. */ private static final List<MediaType> DEFAULT_TYPE_LIST = Collections.singletonList(MediaType.WILDCARD_TYPE); /** WADL media type. */ public static final MediaType WADL_TYPE = new MediaType("application", "vnd.sun.wadl+xml"); /** Prefix of sub-type part of media types as application/*+xml. */ public static final String EXT_PREFIX_SUBTYPE = "*+"; /** Media types as application/atom+* or application/*+xml pattern. */ public static final Pattern EXT_SUBTYPE_PATTERN = Pattern.compile("([^\\+]+)\\+(.+)"); /** Media types as application/atom+* pattern. */ public static final Pattern EXT_SUFFIX_SUBTYPE_PATTERN = Pattern.compile("([^\\+]+)\\+\\*"); /** Media types as application/*+xml pattern. */ public static final Pattern EXT_PREFIX_SUBTYPE_PATTERN = Pattern.compile("\\*\\+(.+)"); /** * Creates range of acceptable media types for look up appropriate {@link MessageBodyReader}, * {@link MessageBodyWriter} or {@link ContextResolver}. It provides set of media types in descending ordering. * <p/> * For given media type: {@code application/xml} * <li>{@code application/xml}</li> * <li>{@code application/*+xml}</li> * <li>{@code application/*}</li> * <li>{@code */*}</li> */ public static Iterator<MediaType> createDescendingMediaTypeIterator(MediaType type) { return new MediaTypeRange(type); } public static final class MediaTypeRange implements Iterator<MediaType> { private MediaType next; public MediaTypeRange(MediaType type) { next = (type == null) ? MediaType.WILDCARD_TYPE : type; } @Override public boolean hasNext() { return next != null; } @Override public MediaType next() { if (next == null) { throw new NoSuchElementException(); } MediaType type = next; fetchNext(); return type; } @Override public void remove() { throw new UnsupportedOperationException(); } void fetchNext() { MediaType current = next; if (current.isWildcardType() && current.isWildcardSubtype()) { next = null; } else if (current.isWildcardSubtype()) { // Type such as 'application/*' . Next one to be checked is '*/*'. // This type is always last for checking in our range. next = MediaType.WILDCARD_TYPE; } else { // Media type such as application/xml, application/atom+xml, application/*+xml or application/xml+* . String type = current.getType(); String subType = current.getSubtype(); Matcher extMatcher = EXT_SUBTYPE_PATTERN.matcher(subType); if (extMatcher.matches()) { // Media type such as application/atom+xml or application/*+xml (sub-type extended!!!) String extSubtypePrefix = extMatcher.group(1); String extSubtype = extMatcher.group(2); if (MEDIA_TYPE_WILDCARD.equals(extSubtypePrefix)) { // Media type such as 'application/*+xml' (first part is wildcard). Next to be checked will be 'application/*'. next = new MediaType(type, MEDIA_TYPE_WILDCARD); } else { // Media type such as 'application/atom+xml' next to be checked will be 'application/*+xml' next = new MediaType(type, EXT_PREFIX_SUBTYPE + extSubtype); } } else { // Media type without extension such as 'application/xml'. // Next will be 'application/*+xml' since extensions should support pure xml as well. next = new MediaType(type, EXT_PREFIX_SUBTYPE + subType); } } } } ; /** * Creates a list of media type for given @Consumes annotation. If parameter mime is {@code null} then returns list with single * element {@link MediaType#WILDCARD_TYPE}. * * @param mime * the Consumes annotation. * @return ordered list of media types. */ public static List<MediaType> createConsumesList(Consumes mime) { if (mime == null) { return DEFAULT_TYPE_LIST; } return createMediaTypesList(mime.value()); } /** * Creates a list of media type for given Produces annotation. If parameter mime is {@code null} then return list with single element * {@link MediaType#WILDCARD_TYPE}. * * @param mime * the Produces annotation. * @return ordered list of media types. */ public static List<MediaType> createProducesList(Produces mime) { if (mime == null) { return DEFAULT_TYPE_LIST; } return createMediaTypesList(mime.value()); } /** * Useful for checking does method able to consume certain media type. * * @param consumes * list of consumed media types * @param contentType * should be checked * @return true contentType is compatible to one of consumes, false otherwise */ public static boolean isConsume(List<MediaType> consumes, MediaType contentType) { return consumes.stream().anyMatch(consume -> isMatched(consume, contentType)); } /** * Create a list of media type from string array. * * @param mimes * source string array * @return ordered list of media types */ private static List<MediaType> createMediaTypesList(String[] mimes) { return Arrays.stream(mimes).map(MediaType::valueOf).sorted(new MediaTypeComparator()).collect(toList()); } /** * Looking for first accept media type that is matched to first media type from {@code producedByResource}. * * @param acceptMediaTypes * See {@link AcceptMediaType} * @param producedByResource * list of produces media type, See {@link Produces} * @return the best found compatible accept media type or {@code null} if media types are not compatible */ public static AcceptMediaType findFistCompatibleAcceptMediaType(List<AcceptMediaType> acceptMediaTypes, List<MediaType> producedByResource) { for (AcceptMediaType accept : acceptMediaTypes) { if (accept.isWildcardType()) { return accept; } for (MediaType produce : producedByResource) { if (produce.isCompatible(accept.getMediaType())) { return accept; } } } return null; } /** * Checks that types {@code mediaTypeOne} and type {@code mediaTypeTwo} are compatible. The operation is commutative. * <p> * Examples: * <ul> * <li><i>text/plain</i> and <i>text/*</i> are compatible</li> * <li><i>application/atom+xml</i> and <i>application/atom+*</i> are compatible</li> * </ul> * </p> * * @param mediaTypeOne * media type * @param mediaTypeTwo * media type * @return {@code true} if types compatible and {@code false} otherwise */ public static boolean isCompatible(MediaType mediaTypeOne, MediaType mediaTypeTwo) { if (mediaTypeOne == null || mediaTypeTwo == null) { throw new IllegalArgumentException("Null arguments are not allowed"); } if (mediaTypeOne.isWildcardType() || mediaTypeTwo.isWildcardType()) { return true; } if (mediaTypeOne.getType().equalsIgnoreCase(mediaTypeTwo.getType())) { if (mediaTypeOne.isWildcardSubtype() || mediaTypeTwo.isWildcardSubtype() || mediaTypeOne.getSubtype().equalsIgnoreCase(mediaTypeTwo.getSubtype())) { return true; } Matcher extSubtypeMatcherOne = EXT_SUBTYPE_PATTERN.matcher(mediaTypeOne.getSubtype()); Matcher extSubtypeMatcherTwo = EXT_SUBTYPE_PATTERN.matcher(mediaTypeTwo.getSubtype()); boolean extSubtypeMatcherOneMatches = extSubtypeMatcherOne.matches(); boolean extSubtypeMatcherTwoMatches = extSubtypeMatcherTwo.matches(); if (!extSubtypeMatcherOneMatches && extSubtypeMatcherTwoMatches) { // one is type such as application/xml // two is type such as application/atom+xml, application/*+xml, application/xml+* return mediaTypeOne.getSubtype().equalsIgnoreCase(extSubtypeMatcherTwo.group(1)) || mediaTypeOne.getSubtype().equalsIgnoreCase(extSubtypeMatcherTwo.group(2)); } else if (extSubtypeMatcherOneMatches && !extSubtypeMatcherTwoMatches) { // one is type such as application/atom+xml, application/*+xml, application/xml+* // two is type such as application/xml return mediaTypeTwo.getSubtype().equalsIgnoreCase(extSubtypeMatcherOne.group(1)) || mediaTypeTwo.getSubtype().equalsIgnoreCase(extSubtypeMatcherOne.group(2)); } else if (extSubtypeMatcherOneMatches && extSubtypeMatcherTwoMatches) { // both types are extended types String subtypePrefixOne = extSubtypeMatcherOne.group(1); String subTypeSuffixOne = extSubtypeMatcherOne.group(2); String subTypePrefixTwo = extSubtypeMatcherTwo.group(1); String subtypeSuffixTwo = extSubtypeMatcherTwo.group(2); if (subtypePrefixOne.equalsIgnoreCase(subTypePrefixTwo) && (MEDIA_TYPE_WILDCARD.equals(subTypeSuffixOne) || MEDIA_TYPE_WILDCARD.equals(subtypeSuffixTwo))) { // parts before '+' are the same and one of after '+' is wildcard '*' // For example two sub-types: atom+* and atom+xml return true; } if (subTypeSuffixOne.equalsIgnoreCase(subtypeSuffixTwo) && (MEDIA_TYPE_WILDCARD.equals(subtypePrefixOne) || MEDIA_TYPE_WILDCARD.equals(subTypePrefixTwo))) { // parts after '+' are the same and one of before '+' is wildcard '*' // For example two sub-types: *+xml and atom+xml return true; } } } return false; } /** * Checks that type {@code checkMe} is matched to type {@code pattern}. NOTE The operation is NOT commutative, * e.g. matching of type {@code checkMe} to {@code pattern} does not guaranty that {@code pattern} is matched to {@code checkMe}. * <p> * Examples: * <ul> * <li><i>text/plain</i> is matched to <i>text/*</i> but type <i>text/*</i> is not matched to <i>text/plain</i></li> * <li><i>application/atom+xml</i> is matched to <i>application/atom+*</i> but type <i>application/atom+*</i> is not * matched to <i>application/atom+xml</i></li> * </ul> * </p> * * @param pattern * pattern type * @param checkMe * type to be checked * @return {@code true} if type {@code checkMe} is matched to {@code pattern} and {@code false} otherwise */ public static boolean isMatched(MediaType pattern, MediaType checkMe) { if (pattern == null || checkMe == null) { throw new IllegalArgumentException("Null arguments are not allowed"); } if (pattern.isWildcardType()) { return true; } if (pattern.getType().equalsIgnoreCase(checkMe.getType())) { if (pattern.isWildcardSubtype() || pattern.getSubtype().equalsIgnoreCase(checkMe.getSubtype())) { return true; } Matcher patternMatcher = EXT_SUBTYPE_PATTERN.matcher(pattern.getSubtype()); Matcher checkMeMatcher = EXT_SUBTYPE_PATTERN.matcher(checkMe.getSubtype()); if (patternMatcher.matches()) { String patternPrefix = patternMatcher.group(1); String patternSuffix = patternMatcher.group(2); if (!checkMeMatcher.matches()) { // pattern is type such as application/atom+xml, application/*+xml, application/xml+* // checkMe is type such as application/xml return checkMe.getSubtype().equalsIgnoreCase(patternPrefix) || checkMe.getSubtype().equalsIgnoreCase(patternSuffix); } // both types are extended types String checkMePrefix = checkMeMatcher.group(1); String checkMeSuffix = checkMeMatcher.group(2); if (MEDIA_TYPE_WILDCARD.equals(patternSuffix) && patternPrefix.equalsIgnoreCase(checkMePrefix)) { // parts before '+' are the same and pattern after '+' is wildcard '*' // For example two sub-types: atom+* and atom+xml return true; } if (MEDIA_TYPE_WILDCARD.equals(patternPrefix) && patternSuffix.equalsIgnoreCase(checkMeSuffix)) { // parts after '+' are the same and pattern before '+' is wildcard '*' // For example two sub-types: *+xml and atom+xml return true; } } } return false; } private MediaTypeHelper() { } }