/* * Copyright 2011 cruxframework.org * * 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 org.cruxframework.crux.core.server.rest.core.registry; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import org.cruxframework.crux.core.server.rest.core.MultivaluedMapImpl; import org.cruxframework.crux.core.server.rest.core.dispatch.ResourceMethod; import org.cruxframework.crux.core.server.rest.spi.HttpRequest; import org.cruxframework.crux.core.server.rest.spi.RestFailure; import org.cruxframework.crux.core.server.rest.spi.NotFoundException; import org.cruxframework.crux.core.server.rest.util.PathHelper; /** * * @author Thiago da Rosa de Bustamante * */ public class RootSegment extends Segment { protected Map<String, SimpleSegment> simpleSegments = new HashMap<String, SimpleSegment>(); protected Map<String, PathParamSegment> resourceExpressions = new HashMap<String, PathParamSegment>(); protected List<PathParamSegment> sortedResourceExpressions = new ArrayList<PathParamSegment>(); protected Map<String, PathParamSegment> locatorExpressions = new HashMap<String, PathParamSegment>(); protected List<PathParamSegment> sortedLocatorExpressions = new ArrayList<PathParamSegment>(); protected Map<String, List<ResourceMethod>> bounded = new LinkedHashMap<String, List<ResourceMethod>>(); /** * Return a map of paths and what resource methods they are bound to * * @return */ public Map<String, List<ResourceMethod>> getBounded() { return bounded; } @Override protected boolean isEmpty() { return super.isEmpty() && simpleSegments.size() == 0 && resourceExpressions.size() == 0 && locatorExpressions.size() == 0; } protected void addPath(String[] segments, int index, ResourceMethod invoker) { String segment = segments[index]; // Regular expressions can have '{' and '}' characters. Replace them to // do match String replacedCurlySegment = PathHelper.replaceEnclosedCurlyBraces(segment); Matcher withPathParam = PathHelper.URI_PARAM_PATTERN.matcher(replacedCurlySegment); if (withPathParam.find()) { String expression = recombineSegments(segments, index); PathParamSegment segmentNode = resourceExpressions.get(expression); if (segmentNode == null) { segmentNode = new PathParamSegment(expression); resourceExpressions.put(segmentNode.getPathExpression(), segmentNode); sortedResourceExpressions.add(segmentNode); Collections.sort(sortedResourceExpressions); } segmentNode.addMethod(invoker); } else { SimpleSegment segmentNode = simpleSegments.get(segment); if (segmentNode == null) { segmentNode = new SimpleSegment(segment); simpleSegments.put(segment, segmentNode); } if (segments.length > index + 1) { segmentNode.addPath(segments, index + 1, invoker); } else { segmentNode.addMethod(invoker); } } } private String recombineSegments(String[] segments, int index) { String expression = ""; boolean first = true; for (int i = index; i < segments.length; i++) { if (first) { first = false; } else { expression += "/"; } expression += segments[i]; } return expression; } public void addPath(String path, ResourceMethod invoker) { List<ResourceMethod> list = bounded.get(path); if (list == null) { list = new ArrayList<ResourceMethod>(); bounded.put(path, list); } list.add(invoker); if (path.startsWith("/")) path = path.substring(1); MultivaluedMapImpl<String, String> pathParamExpr = new MultivaluedMapImpl<String, String>(); StringBuffer newPath = pullPathParamExpressions(path, pathParamExpr); path = newPath.toString(); String[] segments = path.split("/"); for (int i = 0; i < segments.length; i++) { segments[i] = putBackPathParamExpressions(segments[i], pathParamExpr); } addPath(segments, 0, invoker); } protected ResourceMethod matchChildren(HttpRequest request, String path, int start) { String simpleSegment = null; if (start == path.length()) { simpleSegment = ""; } else { int endOfSegmentIndex = path.indexOf('/', start); if (endOfSegmentIndex > -1) simpleSegment = path.substring(start, endOfSegmentIndex); else simpleSegment = path.substring(start); } RestFailure lastFailure = null; SimpleSegment segment = simpleSegments.get(simpleSegment); if (segment != null) { try { return segment.matchSimple(request, path, start); } catch (RestFailure e) { lastFailure = e; } } for (PathParamSegment pathParamSegment : sortedResourceExpressions) { try { return pathParamSegment.matchPattern(request, path, start); } catch (RestFailure e) { // try and propagate matched path that threw non-404 responses, // i.e. MethodNotAllowed, etc. if (lastFailure == null || lastFailure instanceof NotFoundException) lastFailure = e; } } for (PathParamSegment pathParamSegment : sortedLocatorExpressions) { try { return pathParamSegment.matchPattern(request, path, start); } catch (RestFailure e) { // try and propagate matched path that threw non-404 responses, // i.e. MethodNotAllowed, etc. if (lastFailure == null || lastFailure instanceof NotFoundException) lastFailure = e; } } if (lastFailure != null) throw lastFailure; throw new NotFoundException("Could not find resource for relative : " + path + " of full path: " + request.getUri().getRequestUri()); } public ResourceMethod matchRoot(HttpRequest request) { int start = 0; return matchRoot(request, start); } public ResourceMethod matchRoot(HttpRequest request, int start) { String path = request.getUri().getMatchingPath(); if (start < path.length() && path.charAt(start) == '/') start++; return matchChildren(request, path, start); } private static StringBuffer pullPathParamExpressions(String path, MultivaluedMapImpl<String, String> pathParamExpr) { // Regular expressions can have '{' and '}' characters. Replace them to // do match path = PathHelper.replaceEnclosedCurlyBraces(path); Matcher matcher = PathHelper.URI_PARAM_WITH_REGEX_PATTERN.matcher(path); StringBuffer newPath = new StringBuffer(); while (matcher.find()) { String name = matcher.group(1); String regex = matcher.group(3); // Regular expressions can have '{' and '}' characters. Recover // original replacement pathParamExpr.add(name, PathHelper.recoverEnclosedCurlyBraces(regex)); matcher.appendReplacement(newPath, "{$1:x}"); } matcher.appendTail(newPath); return newPath; } private static String putBackPathParamExpressions(String path, MultivaluedMapImpl<String, String> pathParamExpr) { Matcher matcher = PathHelper.URI_PARAM_WITH_REGEX_PATTERN.matcher(path); StringBuffer newPath = new StringBuffer(); while (matcher.find()) { String name = matcher.group(1); String val = pathParamExpr.get(name).remove(0); // double encode slashes, so that slashes stay where they are val = val.replace("\\", "\\\\"); val = val.replace("$", "\\$"); matcher.appendReplacement(newPath, "{$1:" + val + "}"); } matcher.appendTail(newPath); return newPath.toString(); } }