001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.camel.support; 018 019import java.util.ArrayList; 020import java.util.Iterator; 021import java.util.List; 022import java.util.Locale; 023 024/** 025 * A context path matcher when using rest-dsl that allows components to reuse the same matching logic. 026 * <p/> 027 * The component should use the {@link #matchBestPath(String, String, java.util.List)} with the request details 028 * and the matcher returns the best matched, or <tt>null</tt> if none could be determined. 029 * <p/> 030 * The {@link ConsumerPath} is used for the components to provide the details to the matcher. 031 */ 032public final class RestConsumerContextPathMatcher { 033 private RestConsumerContextPathMatcher() { 034 035 } 036 037 038 /** 039 * Consumer path details which must be implemented and provided by the components. 040 */ 041 public interface ConsumerPath<T> { 042 043 /** 044 * Any HTTP restrict method that would not be allowed 045 */ 046 String getRestrictMethod(); 047 048 /** 049 * The consumer context-path which may include wildcards 050 */ 051 String getConsumerPath(); 052 053 /** 054 * The consumer implementation 055 */ 056 T getConsumer(); 057 058 } 059 060 /** 061 * Does the incoming request match the given consumer path (ignore case) 062 * 063 * @param requestPath the incoming request context path 064 * @param consumerPath a consumer path 065 * @param matchOnUriPrefix whether to use the matchOnPrefix option 066 * @return <tt>true</tt> if matched, <tt>false</tt> otherwise 067 */ 068 public static boolean matchPath(String requestPath, String consumerPath, boolean matchOnUriPrefix) { 069 // deal with null parameters 070 if (requestPath == null && consumerPath == null) { 071 return true; 072 } 073 if (requestPath == null || consumerPath == null) { 074 return false; 075 } 076 077 String p1 = requestPath.toLowerCase(Locale.ENGLISH); 078 String p2 = consumerPath.toLowerCase(Locale.ENGLISH); 079 080 if (p1.equals(p2)) { 081 return true; 082 } 083 084 if (matchOnUriPrefix && p1.startsWith(p2)) { 085 return true; 086 } 087 088 return false; 089 } 090 091 /** 092 * Finds the best matching of the list of consumer paths that should service the incoming request. 093 * 094 * @param requestMethod the incoming request HTTP method 095 * @param requestPath the incoming request context path 096 * @param consumerPaths the list of consumer context path details 097 * @return the best matched consumer, or <tt>null</tt> if none could be determined. 098 */ 099 public static ConsumerPath matchBestPath(String requestMethod, String requestPath, List<ConsumerPath> consumerPaths) { 100 ConsumerPath answer = null; 101 102 List<ConsumerPath> candidates = new ArrayList<ConsumerPath>(); 103 104 // first match by http method 105 for (ConsumerPath entry : consumerPaths) { 106 if (matchRestMethod(requestMethod, entry.getRestrictMethod())) { 107 candidates.add(entry); 108 } 109 } 110 111 // then see if we got a direct match 112 Iterator<ConsumerPath> it = candidates.iterator(); 113 while (it.hasNext()) { 114 ConsumerPath consumer = it.next(); 115 if (matchRestPath(requestPath, consumer.getConsumerPath(), false)) { 116 answer = consumer; 117 break; 118 } 119 } 120 121 // then match by wildcard path 122 if (answer == null) { 123 it = candidates.iterator(); 124 while (it.hasNext()) { 125 ConsumerPath consumer = it.next(); 126 // filter non matching paths 127 if (!matchRestPath(requestPath, consumer.getConsumerPath(), true)) { 128 it.remove(); 129 } 130 } 131 132 // if there is multiple candidates with wildcards then pick anyone with the least number of wildcards 133 int bestWildcard = Integer.MAX_VALUE; 134 ConsumerPath best = null; 135 if (candidates.size() > 1) { 136 it = candidates.iterator(); 137 while (it.hasNext()) { 138 ConsumerPath entry = it.next(); 139 int wildcards = countWildcards(entry.getConsumerPath()); 140 if (wildcards > 0) { 141 if (best == null || wildcards < bestWildcard) { 142 best = entry; 143 bestWildcard = wildcards; 144 } 145 } 146 } 147 148 if (best != null) { 149 // pick the best among the wildcards 150 answer = best; 151 } 152 } 153 154 // if there is one left then its our answer 155 if (answer == null && candidates.size() == 1) { 156 answer = candidates.get(0); 157 } 158 } 159 160 return answer; 161 } 162 163 /** 164 * Matches the given request HTTP method with the configured HTTP method of the consumer 165 * 166 * @param method the request HTTP method 167 * @param restrict the consumer configured HTTP restrict method 168 * @return <tt>true</tt> if matched, <tt>false</tt> otherwise 169 */ 170 private static boolean matchRestMethod(String method, String restrict) { 171 if (restrict == null) { 172 return true; 173 } 174 175 return restrict.toLowerCase(Locale.ENGLISH).contains(method.toLowerCase(Locale.ENGLISH)); 176 } 177 178 /** 179 * Matches the given request path with the configured consumer path 180 * 181 * @param requestPath the request path 182 * @param consumerPath the consumer path which may use { } tokens 183 * @return <tt>true</tt> if matched, <tt>false</tt> otherwise 184 */ 185 private static boolean matchRestPath(String requestPath, String consumerPath, boolean wildcard) { 186 // remove starting/ending slashes 187 if (requestPath.startsWith("/")) { 188 requestPath = requestPath.substring(1); 189 } 190 if (requestPath.endsWith("/")) { 191 requestPath = requestPath.substring(0, requestPath.length() - 1); 192 } 193 // remove starting/ending slashes 194 if (consumerPath.startsWith("/")) { 195 consumerPath = consumerPath.substring(1); 196 } 197 if (consumerPath.endsWith("/")) { 198 consumerPath = consumerPath.substring(0, consumerPath.length() - 1); 199 } 200 201 // split using single char / is optimized in the jdk 202 String[] requestPaths = requestPath.split("/"); 203 String[] consumerPaths = consumerPath.split("/"); 204 205 // must be same number of path's 206 if (requestPaths.length != consumerPaths.length) { 207 return false; 208 } 209 210 for (int i = 0; i < requestPaths.length; i++) { 211 String p1 = requestPaths[i]; 212 String p2 = consumerPaths[i]; 213 214 if (wildcard && p2.startsWith("{") && p2.endsWith("}")) { 215 // always matches 216 continue; 217 } 218 219 if (!matchPath(p1, p2, false)) { 220 return false; 221 } 222 } 223 224 // assume matching 225 return true; 226 } 227 228 /** 229 * Counts the number of wildcards in the path 230 * 231 * @param consumerPath the consumer path which may use { } tokens 232 * @return number of wildcards, or <tt>0</tt> if no wildcards 233 */ 234 private static int countWildcards(String consumerPath) { 235 int wildcards = 0; 236 237 // remove starting/ending slashes 238 if (consumerPath.startsWith("/")) { 239 consumerPath = consumerPath.substring(1); 240 } 241 if (consumerPath.endsWith("/")) { 242 consumerPath = consumerPath.substring(0, consumerPath.length() - 1); 243 } 244 245 String[] consumerPaths = consumerPath.split("/"); 246 for (String p2 : consumerPaths) { 247 if (p2.startsWith("{") && p2.endsWith("}")) { 248 wildcards++; 249 } 250 } 251 252 return wildcards; 253 } 254 255}