xref: /unit/src/java/nginx/unit/websocket/server/UriTemplate.java (revision 1157:7ae152bda303)
1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  */
17 package nginx.unit.websocket.server;
18 
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26 
27 import javax.websocket.DeploymentException;
28 
29 import org.apache.tomcat.util.res.StringManager;
30 
31 /**
32  * Extracts path parameters from URIs used to create web socket connections
33  * using the URI template defined for the associated Endpoint.
34  */
35 public class UriTemplate {
36 
37     private static final StringManager sm = StringManager.getManager(UriTemplate.class);
38 
39     private final String normalized;
40     private final List<Segment> segments = new ArrayList<>();
41     private final boolean hasParameters;
42 
43 
UriTemplate(String path)44     public UriTemplate(String path) throws DeploymentException {
45 
46         if (path == null || path.length() ==0 || !path.startsWith("/")) {
47             throw new DeploymentException(
48                     sm.getString("uriTemplate.invalidPath", path));
49         }
50 
51         StringBuilder normalized = new StringBuilder(path.length());
52         Set<String> paramNames = new HashSet<>();
53 
54         // Include empty segments.
55         String[] segments = path.split("/", -1);
56         int paramCount = 0;
57         int segmentCount = 0;
58 
59         for (int i = 0; i < segments.length; i++) {
60             String segment = segments[i];
61             if (segment.length() == 0) {
62                 if (i == 0 || (i == segments.length - 1 && paramCount == 0)) {
63                     // Ignore the first empty segment as the path must always
64                     // start with '/'
65                     // Ending with a '/' is also OK for instances used for
66                     // matches but not for parameterised templates.
67                     continue;
68                 } else {
69                     // As per EG discussion, all other empty segments are
70                     // invalid
71                     throw new IllegalArgumentException(sm.getString(
72                             "uriTemplate.emptySegment", path));
73                 }
74             }
75             normalized.append('/');
76             int index = -1;
77             if (segment.startsWith("{") && segment.endsWith("}")) {
78                 index = segmentCount;
79                 segment = segment.substring(1, segment.length() - 1);
80                 normalized.append('{');
81                 normalized.append(paramCount++);
82                 normalized.append('}');
83                 if (!paramNames.add(segment)) {
84                     throw new IllegalArgumentException(sm.getString(
85                             "uriTemplate.duplicateParameter", segment));
86                 }
87             } else {
88                 if (segment.contains("{") || segment.contains("}")) {
89                     throw new IllegalArgumentException(sm.getString(
90                             "uriTemplate.invalidSegment", segment, path));
91                 }
92                 normalized.append(segment);
93             }
94             this.segments.add(new Segment(index, segment));
95             segmentCount++;
96         }
97 
98         this.normalized = normalized.toString();
99         this.hasParameters = paramCount > 0;
100     }
101 
102 
match(UriTemplate candidate)103     public Map<String,String> match(UriTemplate candidate) {
104 
105         Map<String,String> result = new HashMap<>();
106 
107         // Should not happen but for safety
108         if (candidate.getSegmentCount() != getSegmentCount()) {
109             return null;
110         }
111 
112         Iterator<Segment> candidateSegments =
113                 candidate.getSegments().iterator();
114         Iterator<Segment> targetSegments = segments.iterator();
115 
116         while (candidateSegments.hasNext()) {
117             Segment candidateSegment = candidateSegments.next();
118             Segment targetSegment = targetSegments.next();
119 
120             if (targetSegment.getParameterIndex() == -1) {
121                 // Not a parameter - values must match
122                 if (!targetSegment.getValue().equals(
123                         candidateSegment.getValue())) {
124                     // Not a match. Stop here
125                     return null;
126                 }
127             } else {
128                 // Parameter
129                 result.put(targetSegment.getValue(),
130                         candidateSegment.getValue());
131             }
132         }
133 
134         return result;
135     }
136 
137 
hasParameters()138     public boolean hasParameters() {
139         return hasParameters;
140     }
141 
142 
getSegmentCount()143     public int getSegmentCount() {
144         return segments.size();
145     }
146 
147 
getNormalizedPath()148     public String getNormalizedPath() {
149         return normalized;
150     }
151 
152 
getSegments()153     private List<Segment> getSegments() {
154         return segments;
155     }
156 
157 
158     private static class Segment {
159         private final int parameterIndex;
160         private final String value;
161 
Segment(int parameterIndex, String value)162         public Segment(int parameterIndex, String value) {
163             this.parameterIndex = parameterIndex;
164             this.value = value;
165         }
166 
167 
getParameterIndex()168         public int getParameterIndex() {
169             return parameterIndex;
170         }
171 
172 
getValue()173         public String getValue() {
174             return value;
175         }
176     }
177 }
178