/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 nginx.unit.websocket.server; import java.util.ArrayList; 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 javax.websocket.DeploymentException; import org.apache.tomcat.util.res.StringManager; /** * Extracts path parameters from URIs used to create web socket connections * using the URI template defined for the associated Endpoint. */ public class UriTemplate { private static final StringManager sm = StringManager.getManager(UriTemplate.class); private final String normalized; private final List segments = new ArrayList<>(); private final boolean hasParameters; public UriTemplate(String path) throws DeploymentException { if (path == null || path.length() ==0 || !path.startsWith("/")) { throw new DeploymentException( sm.getString("uriTemplate.invalidPath", path)); } StringBuilder normalized = new StringBuilder(path.length()); Set paramNames = new HashSet<>(); // Include empty segments. String[] segments = path.split("/", -1); int paramCount = 0; int segmentCount = 0; for (int i = 0; i < segments.length; i++) { String segment = segments[i]; if (segment.length() == 0) { if (i == 0 || (i == segments.length - 1 && paramCount == 0)) { // Ignore the first empty segment as the path must always // start with '/' // Ending with a '/' is also OK for instances used for // matches but not for parameterised templates. continue; } else { // As per EG discussion, all other empty segments are // invalid throw new IllegalArgumentException(sm.getString( "uriTemplate.emptySegment", path)); } } normalized.append('/'); int index = -1; if (segment.startsWith("{") && segment.endsWith("}")) { index = segmentCount; segment = segment.substring(1, segment.length() - 1); normalized.append('{'); normalized.append(paramCount++); normalized.append('}'); if (!paramNames.add(segment)) { throw new IllegalArgumentException(sm.getString( "uriTemplate.duplicateParameter", segment)); } } else { if (segment.contains("{") || segment.contains("}")) { throw new IllegalArgumentException(sm.getString( "uriTemplate.invalidSegment", segment, path)); } normalized.append(segment); } this.segments.add(new Segment(index, segment)); segmentCount++; } this.normalized = normalized.toString(); this.hasParameters = paramCount > 0; } public Map match(UriTemplate candidate) { Map result = new HashMap<>(); // Should not happen but for safety if (candidate.getSegmentCount() != getSegmentCount()) { return null; } Iterator candidateSegments = candidate.getSegments().iterator(); Iterator targetSegments = segments.iterator(); while (candidateSegments.hasNext()) { Segment candidateSegment = candidateSegments.next(); Segment targetSegment = targetSegments.next(); if (targetSegment.getParameterIndex() == -1) { // Not a parameter - values must match if (!targetSegment.getValue().equals( candidateSegment.getValue())) { // Not a match. Stop here return null; } } else { // Parameter result.put(targetSegment.getValue(), candidateSegment.getValue()); } } return result; } public boolean hasParameters() { return hasParameters; } public int getSegmentCount() { return segments.size(); } public String getNormalizedPath() { return normalized; } private List getSegments() { return segments; } private static class Segment { private final int parameterIndex; private final String value; public Segment(int parameterIndex, String value) { this.parameterIndex = parameterIndex; this.value = value; } public int getParameterIndex() { return parameterIndex; } public String getValue() { return value; } } }