/* * 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.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.cruxframework.crux.core.server.rest.core.dispatch.ResourceMethod; import org.cruxframework.crux.core.server.rest.spi.BadRequestException; import org.cruxframework.crux.core.server.rest.spi.HttpRequest; import org.cruxframework.crux.core.server.rest.spi.NotFoundException; import org.cruxframework.crux.core.server.rest.spi.UriInfo; import org.cruxframework.crux.core.server.rest.util.Encode; import org.cruxframework.crux.core.server.rest.util.PathHelper; /** * * @author Thiago da Rosa de Bustamante * */ public class PathParamSegment extends Segment implements Comparable<PathParamSegment> { protected String pathExpression; protected String regex; protected Pattern pattern; protected List<Group> groups = new ArrayList<Group>(); protected int literalCharacters; protected int numCapturingGroups; protected int numNonDefaultGroups; private static class Group { int group; String name; private Group(int group, String name) { this.group = group; this.name = name; } } public int compareTo(PathParamSegment pathParamSegment) { // as per spec sort first by literal characters, then numCapturing // groups, then num non-default groups if (literalCharacters > pathParamSegment.literalCharacters) return -1; if (literalCharacters < pathParamSegment.literalCharacters) return 1; if (numCapturingGroups > pathParamSegment.numCapturingGroups) return -1; if (numCapturingGroups < pathParamSegment.numCapturingGroups) return 1; if (numNonDefaultGroups > pathParamSegment.numNonDefaultGroups) return -1; if (numNonDefaultGroups < pathParamSegment.numNonDefaultGroups) return 1; return 0; } // [^?] is in expression to ignore non-capturing group public static final Pattern GROUP = Pattern.compile("[^\\\\]\\([^?]"); /** * Find the number of groups in the regular expression don't count escaped * '(' * * @param regex * @return */ private static int groupCount(String regex) { regex = " " + regex; // add a space because GROUP regex trans to match a // non-preceding slash. // if the grouping characters in the range block ignore them. int idxOpen = regex.indexOf('['); if (idxOpen != -1) { int idxClose = regex.indexOf(']', idxOpen); if (idxClose != -1) { regex = regex.substring(0, idxOpen) + regex.substring(idxClose + 1); } } Matcher matcher = GROUP.matcher(regex); int groupCount = 0; while (matcher.find()) groupCount++; return groupCount; } public PathParamSegment(String segment) { this.pathExpression = segment; String replacedCurlySegment = PathHelper.replaceEnclosedCurlyBraces(segment); literalCharacters = PathHelper.URI_PARAM_PATTERN.matcher(replacedCurlySegment).replaceAll("").length(); String[] split = PathHelper.URI_PARAM_PATTERN.split(replacedCurlySegment); Matcher withPathParam = PathHelper.URI_PARAM_PATTERN.matcher(replacedCurlySegment); int i = 0; StringBuffer buffer = new StringBuffer(); if (i < split.length) buffer.append(Pattern.quote(split[i++])); int groupNumber = 1; while (withPathParam.find()) { String name = withPathParam.group(1); buffer.append("("); if (withPathParam.group(3) == null) { buffer.append("[^/]+"); groups.add(new Group(groupNumber++, name)); } else { String expr = withPathParam.group(3); expr = PathHelper.recoverEnclosedCurlyBraces(expr); buffer.append(expr); numNonDefaultGroups++; groups.add(new Group(groupNumber++, name)); groupNumber += groupCount(expr); } buffer.append(")"); if (i < split.length) buffer.append(Pattern.quote(split[i++])); } regex = buffer.toString(); pattern = Pattern.compile(regex); numCapturingGroups = groups.size(); } public String getRegex() { return regex; } public String getPathExpression() { return pathExpression; } protected void populatePathParams(HttpRequest request, Matcher matcher, String path) { UriInfo uriInfo = (UriInfo) request.getUri(); for (Group group : groups) { String value = matcher.group(group.group); uriInfo.addEncodedPathParameter(group.name, value); int index = matcher.start(group.group); int start = 0; if (path.charAt(0) == '/') start++; int segmentIndex = 0; if (start < path.length()) { int count = 0; for (int i = start; i < index && i < path.length(); i++) { if (path.charAt(i) == '/') count++; } segmentIndex = count; } int numSegments = 1; for (int i = 0; i < value.length(); i++) { if (value.charAt(i) == '/') numSegments++; } if (segmentIndex + numSegments > request.getUri().getPathSegments().size()) { throw new BadRequestException("Number of matched segments greater than actual", "Can not invoke requested service"); } PathSegment[] encodedSegments = new PathSegment[numSegments]; PathSegment[] decodedSegments = new PathSegment[numSegments]; for (int i = 0; i < numSegments; i++) { decodedSegments[i] = request.getUri().getPathSegments().get(segmentIndex + i); encodedSegments[i] = request.getUri().getPathSegments(false).get(segmentIndex + i); } uriInfo.getEncodedPathParameterPathSegments().add(group.name, encodedSegments); uriInfo.getPathParameterPathSegments().add(group.name, decodedSegments); } } public ResourceMethod matchPattern(HttpRequest request, String path, int start) { UriInfo uriInfo = (UriInfo) request.getUri(); Matcher matcher = pattern.matcher(path); matcher.region(start, path.length()); if (matcher.matches()) { // we consumed entire path string ResourceMethod invoker = match(request.getHttpMethod(), request); if (invoker == null) { throw new NotFoundException("Could not find resource for relative : " + path + " of full path: " + request.getUri().getRequestUri()); } uriInfo.pushMatchedURI(path, Encode.decode(path)); populatePathParams(request, matcher, path); return invoker; } throw new NotFoundException("Could not find resource for relative : " + path + " of full path: " + request.getUri().getRequestUri()); } public static int pathSegmentIndex(String string, int start, int stop) { if (start >= string.length()) return 0; int count = 0; for (int i = start; i < stop && i < string.length(); i++) { if (string.charAt(i) == '/') count++; } return count; } }