/**
* Copyright 2005-2014 Restlet
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can
* select the license that you prefer but you may not use this file except in
* compliance with one of these Licenses.
*
* You can obtain a copy of the Apache 2.0 license at
* http://www.opensource.org/licenses/apache-2.0
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://restlet.com/products/restlet-framework
*
* Restlet is a registered trademark of Restlet S.A.S.
*/
package org.restlet.ext.jaxrs.internal.util;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.ext.jaxrs.internal.core.CallContext;
import org.restlet.ext.jaxrs.internal.wrappers.ResourceMethod;
import org.restlet.ext.jaxrs.internal.wrappers.RrcOrRml;
import org.restlet.ext.jaxrs.internal.wrappers.SubResourceLocator;
/**
* This class contains helper methods for the algorithm in
* {@link org.restlet.ext.jaxrs.JaxRsRestlet}.
*
* @author Stephan Koops
*/
public class AlgorithmUtil {
private static enum ConsOrProdMime {
/**
* Declares that the methods etc. for the consume mime shoud be used
*/
CONSUME_MIME,
/**
* Declares that the methods etc. for the produced mime shoud be used
*/
PRODUCE_MIME
}
/**
* Sorts the ResourceMethods by it's number of non default regular
* expressions
*/
private static Comparator<ResourceMethod> COMP = new Comparator<ResourceMethod>() {
public int compare(ResourceMethod rm1, ResourceMethod rm2) {
int nndre1 = rm1.getPathRegExp().getNoNonDefCaprGroups();
int nndre2 = rm2.getPathRegExp().getNoNonDefCaprGroups();
return nndre2 - nndre1;
}
};
/**
* Adds the matched template parameters to the {@link CallContext}.
*
* @param matchResult
* @param callContext
* Contains the encoded template Parameters, that are read from
* the called URI, the Restlet {@link Request} and the Restlet
* {@link Response}.
*/
public static void addPathVarsToMap(MatchingResult matchResult,
CallContext callContext) {
final Map<String, String> variables = matchResult.getVariables();
for (final Map.Entry<String, String> varEntry : variables.entrySet()) {
final String key = varEntry.getKey();
final String value = varEntry.getValue();
callContext.addPathParamsEnc(key, value);
}
}
private static OrderedMap<ResourceMethod, List<MediaType>> findMethodsSupportAllTypes(
Collection<ResourceMethod> resourceMethods, ConsOrProdMime inOut) {
final OrderedMap<ResourceMethod, List<MediaType>> returnMethods = new OrderedMap<ResourceMethod, List<MediaType>>();
for (final ResourceMethod resourceMethod : resourceMethods) {
final List<MediaType> mimes = getConsOrProdMimes(resourceMethod,
inOut);
for (final MediaType resMethMediaType : mimes) {
if (resMethMediaType.equals(MediaType.ALL)) {
returnMethods.put(resourceMethod, mimes);
}
}
}
return returnMethods;
}
private static OrderedMap<ResourceMethod, List<MediaType>> findMethodsSupportType(
Collection<ResourceMethod> resourceMethods, ConsOrProdMime inOut,
SortedMetadata<MediaType> mediaTypes) {
final OrderedMap<ResourceMethod, List<MediaType>> returnMethods = new OrderedMap<ResourceMethod, List<MediaType>>();
for (final ResourceMethod resourceMethod : resourceMethods) {
final List<MediaType> mimes = getConsOrProdMimes(resourceMethod,
inOut);
for (final MediaType resMethMediaType : mimes) {
for (final MediaType mediaType : mediaTypes) {
final String resMethMainType = resMethMediaType
.getMainType();
final String wishedMainType = mediaType.getMainType();
if (resMethMainType.equals(wishedMainType)) {
returnMethods.put(resourceMethod, mimes);
}
}
}
}
return returnMethods;
}
/**
* @param resourceMethods
* @param inOut
* @param mediaType
* @return Never returns null.
*/
private static OrderedMap<ResourceMethod, List<MediaType>> findMethodsSupportTypeAndSubType(
Collection<ResourceMethod> resourceMethods, ConsOrProdMime inOut,
SortedMetadata<MediaType> mediaTypes) {
final OrderedMap<ResourceMethod, List<MediaType>> returnMethods = new OrderedMap<ResourceMethod, List<MediaType>>();
for (final ResourceMethod resourceMethod : resourceMethods) {
final List<MediaType> mimes = getConsOrProdMimes(resourceMethod,
inOut);
for (final MediaType resMethMediaType : mimes) {
for (final MediaType mediaType : mediaTypes) {
if (resMethMediaType.equals(mediaType, true)) {
returnMethods.put(resourceMethod, mimes);
}
}
}
}
return returnMethods;
}
/**
* @param resourceMethods
* @param inOut
* @param mediaType
* @return
*/
private static OrderedMap<ResourceMethod, List<MediaType>> findMethodSupportsMime(
Collection<ResourceMethod> resourceMethods, ConsOrProdMime inOut,
SortedMetadata<MediaType> mediaTypes) {
if ((mediaTypes == null) || mediaTypes.isEmpty()) {
return findMethodsSupportAllTypes(resourceMethods, inOut);
}
OrderedMap<ResourceMethod, List<MediaType>> mms;
mms = findMethodsSupportTypeAndSubType(resourceMethods, inOut,
mediaTypes);
if (mms.isEmpty()) {
mms = findMethodsSupportType(resourceMethods, inOut, mediaTypes);
if (mms.isEmpty()) {
mms = findMethodsSupportAllTypes(resourceMethods, inOut);
}
}
return mms;
}
/**
* Sort by using the media type of input data as the primary key and the
* media type of output data as the secondary key.<br>
* Sorting of media types follows the general rule: x/y < x/* < *<!---->/*,
* i.e. a method that explicitly lists one of the requested media types is
* sorted before a method that lists *<!---->/*. Quality parameter values
* are also used such that x/y;q=1.0 < x/y;q=0.7. <br>
* See JSR-311 Spec, section 2.6, Part 3b+c. <br>
* Never returns null.
*
* @param unsortedResourceMethods
* the resourceMethods that provide the required mediaType
* @param givenMediaType
* The MediaType of the given entity.
* @param accMediaTypes
* The accepted MediaTypes
* @param requHttpMethod
* The HTTP method of the request.
* @return Returns the method who best matches the given and accepted media
* type in the request, or null
*/
public static ResourceMethod getBestMethod(
Collection<ResourceMethod> unsortedResourceMethods,
MediaType givenMediaType, SortedMetadata<MediaType> accMediaTypes,
Method requHttpMethod) {
final Collection<ResourceMethod> resourceMethods;
resourceMethods = new SortedOrderedBag<ResourceMethod>(COMP,
unsortedResourceMethods);
// 3 b+c
SortedMetadata<MediaType> givenMediaTypes;
if (givenMediaType != null) {
givenMediaTypes = SortedMetadata.singleton(givenMediaType);
} else {
givenMediaTypes = null;
}
// mms = methods that support the given MediaType
OrderedMap<ResourceMethod, List<MediaType>> mms1;
mms1 = findMethodSupportsMime(resourceMethods,
ConsOrProdMime.CONSUME_MIME, givenMediaTypes);
if (mms1.size() == 1) {
return Util.getFirstKey(mms1);
}
if (mms1.isEmpty()) {
return Util.getFirstElement(resourceMethods);
}
// check for method with best Produces (secondary key)
// mms = Methods support given MediaType and requested MediaType
Map<ResourceMethod, List<MediaType>> mms2;
mms2 = findMethodSupportsMime(mms1.keySet(),
ConsOrProdMime.PRODUCE_MIME, accMediaTypes);
if (mms2.size() == 1) {
return Util.getFirstKey(mms2);
}
if (mms2.isEmpty()) {
return Util.getFirstKey(mms1);
}
for (final MediaType accMediaType : accMediaTypes) {
ResourceMethod bestResMethod = null;
for (final Map.Entry<ResourceMethod, List<MediaType>> mm : mms2
.entrySet()) {
for (final MediaType methodMediaType : mm.getValue()) {
if (accMediaType.includes(methodMediaType)) {
final ResourceMethod currentResMethod = mm.getKey();
if (bestResMethod == null) {
bestResMethod = currentResMethod;
} else {
if (requHttpMethod.equals(Method.HEAD)) {
// special handling for HEAD
final Method bestMethodHttp;
bestMethodHttp = bestResMethod.getHttpMethod();
if (bestMethodHttp.equals(Method.GET)
&& currentResMethod.getHttpMethod()
.equals(Method.HEAD)) {
// ignore HEAD method
} else if (bestMethodHttp.equals(Method.HEAD)
&& currentResMethod.getHttpMethod()
.equals(Method.GET)) {
bestResMethod = currentResMethod;
} else {
// use one of the methods, e.g. the first
}
} else {
// use one of the methods, e.g. the first
}
}
}
}
}
if (bestResMethod != null) {
return bestResMethod;
}
}
return Util.getFirstKey(mms2);
}
/**
* @param resourceMethod
* @param inOut
* @return
*/
private static List<MediaType> getConsOrProdMimes(
ResourceMethod resourceMethod, ConsOrProdMime inOut) {
if (inOut.equals(ConsOrProdMime.CONSUME_MIME)) {
return resourceMethod.getConsumedMimes();
}
final List<MediaType> producedMimes = resourceMethod.getProducedMimes();
if (producedMimes.isEmpty()) {
return Util.createList(MediaType.ALL);
}
return producedMimes;
}
/**
* Implementation of algorithm in JSR-311-Spec, Revision 151, Version
* 2008-08-27, Section 3.7.2, Part 1.e and nearly the same part 2f+2g.<br>
* Sort E using
* <ol>
* <li>the number of literal characters in each member as the primary key
* (descending order),</li>
* <li>the number of capturing groups as a secondary key (descending order),
* </li>
* <li>the number of capturing groups with non-default regular expressions
* (i.e. not "([^/]+?)") as the tertiary key (descending order), and</li>
* <li>the source of each member as quaternary key sorting those derived
* from T<sub>method</sub> ahead of those derived from T<sub>locator</sub>.</li>
* </ol>
*
* @param <R>
*
* @param rrcOrRmls
* Collection of Sub-ResourceMethods and SubResourceLocators or
* root resource class wrappers.
* @return the resource method or sub resource locator or root resource
* class, or null, if the Map is null or empty.
*/
public static <R extends RrcOrRml> R getFirstByNoOfLiteralCharsNoOfCapturingGroups(
Collection<R> rrcOrRmls) {
if ((rrcOrRmls == null) || rrcOrRmls.isEmpty()) {
return null;
}
final Iterator<R> srmlIter = rrcOrRmls.iterator();
R bestSrml = srmlIter.next();
if (rrcOrRmls.size() == 1) {
return bestSrml;
}
int bestSrmlChars = Integer.MIN_VALUE;
int bestSrmlNoCaptGroups = Integer.MIN_VALUE;
int bestSrmlNoNonDefCaptGroups = Integer.MIN_VALUE;
for (final R srml : rrcOrRmls) {
final PathRegExp srmlRegExp = srml.getPathRegExp();
final int srmlNoLitChars = srmlRegExp.getNoOfLiteralChars();
final int srmlNoCaptGroups = srmlRegExp.getNoOfCapturingGroups();
final int srmlNoNonDefCaptGroups = srmlRegExp
.getNoNonDefCaprGroups();
if (srmlNoLitChars > bestSrmlChars) {
bestSrml = srml;
bestSrmlChars = srmlNoLitChars;
bestSrmlNoCaptGroups = srmlNoCaptGroups;
bestSrmlNoNonDefCaptGroups = srmlNoNonDefCaptGroups;
continue;
}
if (srmlNoLitChars == bestSrmlChars) {
if (srmlNoCaptGroups > bestSrmlNoCaptGroups) {
bestSrml = srml;
bestSrmlChars = srmlNoLitChars;
bestSrmlNoCaptGroups = srmlNoCaptGroups;
bestSrmlNoNonDefCaptGroups = srmlNoNonDefCaptGroups;
continue;
}
if (srmlNoCaptGroups == bestSrmlNoCaptGroups) {
if (srmlNoNonDefCaptGroups > bestSrmlNoNonDefCaptGroups) {
bestSrml = srml;
bestSrmlChars = srmlNoLitChars;
bestSrmlNoCaptGroups = srmlNoCaptGroups;
bestSrmlNoNonDefCaptGroups = srmlNoNonDefCaptGroups;
continue;
}
if (srmlNoCaptGroups == bestSrmlNoCaptGroups) {
if ((srml instanceof ResourceMethod)
&& (bestSrml instanceof SubResourceLocator)) {
// prefare methods ahead locators
bestSrml = srml;
bestSrmlChars = srmlNoLitChars;
bestSrmlNoCaptGroups = srmlNoCaptGroups;
bestSrmlNoNonDefCaptGroups = srmlNoNonDefCaptGroups;
continue;
}
}
}
}
}
return bestSrml;
}
/**
* Removes the {@link ResourceMethod}s from the collection, that do not
* support the given HTTP method.
*
* @param resourceMethods
* the collection of {@link ResourceMethod}s.
* @param httpMethod
* the HTTP {@link Method}
* @param alsoGet
* if true, also methods suporting GET are included, also if
* another HTTP method is required. It is intended to be used for
* HEAD requests.
*/
public static void removeNotSupportedHttpMethod(
Collection<ResourceMethod> resourceMethods,
org.restlet.data.Method httpMethod, boolean alsoGet) {
final Iterator<ResourceMethod> methodIter = resourceMethods.iterator();
while (methodIter.hasNext()) {
final ResourceMethod resourceMethod = methodIter.next();
if (!resourceMethod.isHttpMethodSupported(httpMethod, alsoGet)) {
methodIter.remove();
}
}
}
}