package org.jboss.resteasy.core.registry;
import org.jboss.resteasy.core.ResourceInvoker;
import org.jboss.resteasy.core.ResourceLocatorInvoker;
import org.jboss.resteasy.core.ResourceMethodInvoker;
import org.jboss.resteasy.resteasy_jaxrs.i18n.LogMessages;
import org.jboss.resteasy.resteasy_jaxrs.i18n.Messages;
import org.jboss.resteasy.spi.DefaultOptionsMethodException;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.ResteasyUriInfo;
import org.jboss.resteasy.util.HttpHeaderNames;
import org.jboss.resteasy.util.HttpResponseCodes;
import org.jboss.resteasy.util.MediaTypeHelper;
import org.jboss.resteasy.util.WeightedMediaType;
import javax.ws.rs.NotAcceptableException;
import javax.ws.rs.NotAllowedException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.NotSupportedException;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Bill Burke
* @version $Revision: 1 $
*/
public class SegmentNode
{
public static final String RESTEASY_CHOSEN_ACCEPT = "RESTEASY_CHOSEN_ACCEPT";
public static final MediaType[] WILDCARD_ARRAY = {MediaType.WILDCARD_TYPE};
public static final List DEFAULT_ACCEPTS = new ArrayList();
static
{
DEFAULT_ACCEPTS.add(MediaType.WILDCARD_TYPE);
}
protected String segment;
protected Map children = new HashMap();
protected List targets = new ArrayList();
public SegmentNode(String segment)
{
this.segment = segment;
}
protected static class Match
{
MethodExpression expression;
Matcher matcher;
public Match(MethodExpression expression, Matcher matcher)
{
this.expression = expression;
this.matcher = matcher;
}
}
public ResourceInvoker match(HttpRequest request, int start)
{
String path = request.getUri().getMatchingPath();
if (start < path.length() && path.charAt(start) == '/') start++;
List potentials = new ArrayList();
potentials(path, start, potentials);
Collections.sort(potentials);
boolean expressionMatched = false;
List matches = new ArrayList();
for (MethodExpression expression : potentials)
{
// We ignore locators if the first match was a resource method as per the spec Section 3, Step 2(h)
if (expressionMatched && expression.isLocator()) continue;
Pattern pattern = expression.getPattern();
Matcher matcher = pattern.matcher(path);
matcher.region(start, path.length());
if (matcher.matches())
{
expressionMatched = true;
ResourceInvoker invoker = expression.getInvoker();
if (invoker instanceof ResourceLocatorInvoker)
{
ResteasyUriInfo uriInfo = (ResteasyUriInfo) request.getUri();
int length = matcher.start(expression.getNumGroups() + 1);
if (length == -1)
{
uriInfo.pushMatchedPath(path);
uriInfo.pushMatchedURI(path);
}
else
{
// must find the end of the matched pattern
// and get the substring from 1st char thru end
// of matched chars
Pattern p = expression.getPattern();
Matcher m = p.matcher(path);
m.region(start, path.length());
String substring = path;
while(m.find()) {
String endText = m.group(m.groupCount());
if (endText != null && !endText.isEmpty()) {
int indx = path.indexOf(endText, length);
if (indx > -1) {
substring = path.substring(0, indx);
}
}
}
uriInfo.pushMatchedPath(substring);
uriInfo.pushMatchedURI(substring);
}
expression.populatePathParams(request, matcher, path);
return invoker;
}
else
{
matches.add(new Match(expression, matcher));
}
}
}
if (matches.size() == 0)
{
throw new NotFoundException(Messages.MESSAGES.couldNotFindResourceForFullPath(request.getUri().getRequestUri()));
}
Match match = match(matches, request.getHttpMethod(), request);
match.expression.populatePathParams(request, match.matcher, path);
return match.expression.getInvoker();
}
public void potentials(String path, int start, List matches)
{
if (start == path.length()) // we've reached end of string
{
matches.addAll(targets);
return;
}
if (start < path.length())
{
String simpleSegment = null;
int endOfSegmentIndex = path.indexOf('/', start);
if (endOfSegmentIndex > -1) simpleSegment = path.substring(start, endOfSegmentIndex);
else simpleSegment = path.substring(start);
SegmentNode child = children.get(simpleSegment);
if (child != null)
{
int next = start + simpleSegment.length();
if (endOfSegmentIndex > -1) next++; // go past '/'
child.potentials(path, next, matches);
}
}
for (MethodExpression exp : targets)
{
// skip any static matches as they will not match anyways
if (exp.getNumGroups() > 0 || exp.getInvoker() instanceof ResourceLocatorInvoker)
{
matches.add(exp);
}
}
}
public static class SortFactor
{
public float q = 1.0f;
public float qs = 1.0f;
public int d;
public int dm;
public String type;
public String subtype;
public Map params;
public boolean isWildcardType()
{
return type.equals(MediaType.MEDIA_TYPE_WILDCARD);
}
public boolean isWildcardSubtype()
{
return subtype.equals(MediaType.MEDIA_TYPE_WILDCARD);
}
}
public static SortFactor createSortFactor(MediaType client, MediaType server)
{
SortFactor sortFactor = new SortFactor();
if (client.isWildcardType() != server.isWildcardType())
{
sortFactor.type = (client.isWildcardType()) ? server.getType() : client.getType();
sortFactor.d++;
}
else
{
sortFactor.type = client.getType();
}
if (client.isWildcardSubtype() != server.isWildcardSubtype())
{
sortFactor.subtype = (client.isWildcardSubtype()) ? server.getSubtype() : client.getSubtype();
sortFactor.d++;
}
else
{
sortFactor.subtype = client.getSubtype();
}
String q = client.getParameters().get("q");
if (q != null) sortFactor.q = Float.parseFloat(q);
String qs = server.getParameters().get("qs");
if (qs != null) sortFactor.qs = Float.parseFloat(qs);
sortFactor.dm = 0;
for (Map.Entry entry : client.getParameters().entrySet())
{
String name = entry.getKey();
if ("q".equals(name)
|| "qs".equals(name)) continue;
String val = server.getParameters().get(name);
if (val == null)
{
sortFactor.dm++;
continue;
}
if (!val.equals(entry.getValue()))
{
sortFactor.dm++;
continue;
}
}
for (Map.Entry entry : server.getParameters().entrySet())
{
String name = entry.getKey();
if ("q".equals(name)
|| "qs".equals(name)) continue;
String val = client.getParameters().get(name);
if (val == null)
{
sortFactor.dm++;
continue;
}
if (!val.equals(entry.getValue()))
{
sortFactor.dm++;
continue;
}
}
return sortFactor;
}
protected class SortEntry implements Comparable
{
Match match;
MediaType serverProduce;
SortFactor consumes;
SortFactor produces;
public SortEntry(Match match, SortFactor consumes, SortFactor produces, MediaType serverProduce)
{
this.serverProduce = serverProduce;
this.match = match;
this.consumes = consumes;
this.produces = produces;
}
public MediaType getAcceptType()
{
// take params from produce and type and subtype from sort factor
// to define the returned media type
Map params = new HashMap();
for (Map.Entry entry : serverProduce.getParameters().entrySet())
{
String name = entry.getKey();
if ("q".equals(name)
|| "qs".equals(name)) continue;
params.put(name, entry.getValue());
}
Annotation[] annotations = match.expression.invoker.getMethod().getAnnotations();
boolean hasProduces = false;
for (Annotation annotation : annotations)
{
if (annotation instanceof Produces)
{
hasProduces = true;
break;
}
}
if (!hasProduces)
{
annotations = match.expression.invoker.getMethod().getClass().getAnnotations();
for (Annotation annotation : annotations)
{
if (annotation instanceof Produces)
{
hasProduces = true;
break;
}
}
}
if (hasProduces)
{
params.put("resteasy-has-produces", "true");
}
return new MediaType(produces.type, produces.subtype, params);
}
@Override
public int compareTo(SortEntry o)
{
if (consumes.isWildcardType() && !o.consumes.isWildcardType()) return 1;
if (!consumes.isWildcardType() && o.consumes.isWildcardType()) return -1;
if (consumes.isWildcardSubtype() && !o.consumes.isWildcardSubtype()) return 1;
if (!consumes.isWildcardSubtype() && o.consumes.isWildcardSubtype()) return -1;
if (consumes.q > o.consumes.q) return -1;
if (consumes.q < o.consumes.q) return 1;
if (consumes.qs > o.consumes.qs) return -1;
if (consumes.qs < o.consumes.qs) return 1;
if (consumes.d < o.consumes.d) return -1;
if (consumes.d > o.consumes.d) return 1;
if (consumes.dm < o.consumes.dm) return -1;
if (consumes.dm > o.consumes.dm) return 1;
if (produces.isWildcardType() && !o.produces.isWildcardType()) return 1;
if (!produces.isWildcardType() && o.produces.isWildcardType()) return -1;
if (produces.isWildcardSubtype() && !o.produces.isWildcardSubtype()) return 1;
if (!produces.isWildcardSubtype() && o.produces.isWildcardSubtype()) return -1;
if (produces.q > o.produces.q) return -1;
if (produces.q < o.produces.q) return 1;
if (produces.qs > o.produces.qs) return -1;
if (produces.qs < o.produces.qs) return 1;
if (produces.d < o.produces.d) return -1;
if (produces.d > o.produces.d) return 1;
if (produces.dm < o.produces.dm) return -1;
if (produces.dm > o.produces.dm) return 1;
return match.expression.compareTo(o.match.expression);
}
}
public Match match(List matches, String httpMethod, HttpRequest request)
{
MediaType contentType = request.getHttpHeaders().getMediaType();
List requestAccepts = request.getHttpHeaders().getAcceptableMediaTypes();
List weightedAccepts = new ArrayList();
for (MediaType accept : requestAccepts) weightedAccepts.add(WeightedMediaType.parse(accept));
List list = new ArrayList();
boolean methodMatch = false;
boolean consumeMatch = false;
// make a list of all compatible ResourceMethods
for (Match match : matches)
{
ResourceMethodInvoker invoker = (ResourceMethodInvoker) match.expression.getInvoker();
if (invoker.getHttpMethods().contains(httpMethod.toUpperCase()))
{
methodMatch = true;
if (invoker.doesConsume(contentType))
{
consumeMatch = true;
if (invoker.doesProduce(weightedAccepts))
{
list.add(match);
}
}
}
}
if (list.size() == 0)
{
if (!methodMatch)
{
HashSet allowed = new HashSet();
for (Match match : matches)
allowed.addAll(((ResourceMethodInvoker) match.expression.getInvoker()).getHttpMethods());
if (httpMethod.equalsIgnoreCase("HEAD") && allowed.contains("GET"))
{
return match(matches, "GET", request);
}
if (allowed.contains("GET")) allowed.add("HEAD");
allowed.add("OPTIONS");
String allowHeaderValue = "";
boolean first = true;
for (String allow : allowed)
{
if (first) first = false;
else allowHeaderValue += ", ";
allowHeaderValue += allow;
}
if (httpMethod.equals("OPTIONS"))
{
Response res = Response.ok(allowHeaderValue, MediaType.TEXT_PLAIN_TYPE).header(HttpHeaderNames.ALLOW, allowHeaderValue).build();
throw new DefaultOptionsMethodException(Messages.MESSAGES.noResourceMethodFoundForOptions(), res);
}
else
{
Response res = Response.status(HttpResponseCodes.SC_METHOD_NOT_ALLOWED).header(HttpHeaderNames.ALLOW, allowHeaderValue).build();
throw new NotAllowedException(Messages.MESSAGES.noResourceMethodFoundForHttpMethod(httpMethod), res);
}
}
else if (!consumeMatch)
{
throw new NotSupportedException(Messages.MESSAGES.cannotConsumeContentType());
}
throw new NotAcceptableException(Messages.MESSAGES.noMatchForAcceptHeader());
}
//if (list.size() == 1) return list.get(0); //don't do this optimization as we need to set chosen accept
List sortList = new ArrayList();
Set targetMethods = null;
Method firstTargetMethod = null;
for (Match match : list)
{
ResourceMethodInvoker invoker = (ResourceMethodInvoker) match.expression.getInvoker();
if (contentType == null) contentType = MediaType.WILDCARD_TYPE;
MediaType[] consumes = invoker.getConsumes();
if (consumes.length == 0)
{
consumes = WILDCARD_ARRAY;
}
MediaType[] produces = invoker.getProduces();
if (produces.length == 0)
{
produces = WILDCARD_ARRAY;
}
List consumeCombo = new ArrayList();
for (MediaType consume : consumes)
{
consumeCombo.add(createSortFactor(contentType, consume));
}
for (MediaType produce : produces)
{
List acceptableMediaTypes = requestAccepts;
if (acceptableMediaTypes.size() == 0)
{
acceptableMediaTypes = DEFAULT_ACCEPTS;
}
for (MediaType accept : acceptableMediaTypes)
{
if (accept.isCompatible(produce))
{
SortFactor sortFactor = createSortFactor(accept, produce);
for (SortFactor consume : consumeCombo)
{
final Method m = match.expression.getInvoker().getMethod();
sortList.add(new SortEntry(match, consume, sortFactor, produce));
if (firstTargetMethod == null) {
firstTargetMethod = m;
} else if (firstTargetMethod != m) {
if (targetMethods == null) {
targetMethods = new HashSet();
targetMethods.add(firstTargetMethod);
}
targetMethods.add(m);
}
}
}
}
}
}
Collections.sort(sortList);
SortEntry sortEntry = sortList.get(0);
if (targetMethods != null && targetMethods.size() > 1)
{
LogMessages.LOGGER.multipleMethodsMatch(requestToString(request), methodNames(targetMethods));
}
request.setAttribute(RESTEASY_CHOSEN_ACCEPT, sortEntry.getAcceptType());
return sortEntry.match;
}
protected void addExpression(MethodExpression expression)
{
targets.add(expression);
Collections.sort(targets);
}
private String requestToString(HttpRequest request) {
return "\"" + request.getHttpMethod() + " " + request.getUri().getPath() + "\"";
}
private String[] methodNames(Collection methods) {
String[] names = new String[methods.size()];
Iterator iterator = methods.iterator();
int i = 0;
while (iterator.hasNext()) {
names[i++] = iterator.next().toString();
}
return names;
}
}