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 */ 017 package org.apache.camel.util; 018 019 import java.io.UnsupportedEncodingException; 020 import java.net.URI; 021 import java.net.URISyntaxException; 022 import java.net.URLDecoder; 023 import java.net.URLEncoder; 024 import java.util.ArrayList; 025 import java.util.Collections; 026 import java.util.LinkedHashMap; 027 import java.util.List; 028 import java.util.Map; 029 030 /** 031 * URI utilities. 032 * 033 * @version $Revision: 795369 $ 034 */ 035 public final class URISupport { 036 037 private static final String CHARSET = "UTF-8"; 038 039 private URISupport() { 040 // Helper class 041 } 042 043 /** 044 * Holder to get parts of the URI. 045 */ 046 public static class CompositeData { 047 public String host; 048 049 String scheme; 050 String path; 051 URI components[]; 052 Map parameters; 053 String fragment; 054 055 public URI[] getComponents() { 056 return components; 057 } 058 059 public String getFragment() { 060 return fragment; 061 } 062 063 public Map getParameters() { 064 return parameters; 065 } 066 067 public String getScheme() { 068 return scheme; 069 } 070 071 public String getPath() { 072 return path; 073 } 074 075 public String getHost() { 076 return host; 077 } 078 079 public URI toURI() throws URISyntaxException { 080 StringBuffer sb = new StringBuffer(); 081 if (scheme != null) { 082 sb.append(scheme); 083 sb.append(':'); 084 } 085 086 if (host != null && host.length() != 0) { 087 sb.append(host); 088 } else { 089 sb.append('('); 090 for (int i = 0; i < components.length; i++) { 091 if (i != 0) { 092 sb.append(','); 093 } 094 sb.append(components[i].toString()); 095 } 096 sb.append(')'); 097 } 098 099 if (path != null) { 100 sb.append('/'); 101 sb.append(path); 102 } 103 if (!parameters.isEmpty()) { 104 sb.append("?"); 105 sb.append(createQueryString(parameters)); 106 } 107 if (fragment != null) { 108 sb.append("#"); 109 sb.append(fragment); 110 } 111 return new URI(sb.toString()); 112 } 113 } 114 115 @SuppressWarnings("unchecked") 116 public static Map parseQuery(String uri) throws URISyntaxException { 117 try { 118 // use a linked map so the parameters is in the same order 119 Map rc = new LinkedHashMap(); 120 if (uri != null) { 121 String[] parameters = uri.split("&"); 122 for (String parameter : parameters) { 123 int p = parameter.indexOf("="); 124 if (p >= 0) { 125 String name = URLDecoder.decode(parameter.substring(0, p), CHARSET); 126 String value = URLDecoder.decode(parameter.substring(p + 1), CHARSET); 127 rc.put(name, value); 128 } else { 129 rc.put(parameter, null); 130 } 131 } 132 } 133 return rc; 134 } catch (UnsupportedEncodingException e) { 135 URISyntaxException se = new URISyntaxException(e.toString(), "Invalid encoding"); 136 se.initCause(e); 137 throw se; 138 } 139 } 140 141 public static Map parseParameters(URI uri) throws URISyntaxException { 142 String query = uri.getQuery(); 143 if (query == null) { 144 String schemeSpecificPart = uri.getSchemeSpecificPart(); 145 int idx = schemeSpecificPart.lastIndexOf('?'); 146 if (idx < 0) { 147 return Collections.EMPTY_MAP; 148 } else { 149 query = schemeSpecificPart.substring(idx + 1); 150 } 151 } else { 152 query = stripPrefix(query, "?"); 153 } 154 return parseQuery(query); 155 } 156 157 /** 158 * Removes any URI query from the given uri 159 */ 160 public static URI removeQuery(URI uri) throws URISyntaxException { 161 return createURIWithQuery(uri, null); 162 } 163 164 /** 165 * Creates a URI with the given query 166 */ 167 public static URI createURIWithQuery(URI uri, String query) throws URISyntaxException { 168 return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), 169 query, uri.getFragment()); 170 } 171 172 public static CompositeData parseComposite(URI uri) throws URISyntaxException { 173 174 CompositeData rc = new CompositeData(); 175 rc.scheme = uri.getScheme(); 176 String ssp = stripPrefix(uri.getSchemeSpecificPart().trim(), "//").trim(); 177 178 parseComposite(uri, rc, ssp); 179 180 rc.fragment = uri.getFragment(); 181 return rc; 182 } 183 184 private static void parseComposite(URI uri, CompositeData rc, String ssp) throws URISyntaxException { 185 String componentString; 186 String params; 187 188 if (!checkParenthesis(ssp)) { 189 throw new URISyntaxException(uri.toString(), "Not a matching number of '(' and ')' parenthesis"); 190 } 191 192 int p; 193 int intialParen = ssp.indexOf('('); 194 if (intialParen == 0) { 195 rc.host = ssp.substring(0, intialParen); 196 p = rc.host.indexOf("/"); 197 if (p >= 0) { 198 rc.path = rc.host.substring(p); 199 rc.host = rc.host.substring(0, p); 200 } 201 p = ssp.lastIndexOf(')'); 202 componentString = ssp.substring(intialParen + 1, p); 203 params = ssp.substring(p + 1).trim(); 204 } else { 205 componentString = ssp; 206 params = ""; 207 } 208 209 String components[] = splitComponents(componentString); 210 rc.components = new URI[components.length]; 211 for (int i = 0; i < components.length; i++) { 212 rc.components[i] = new URI(components[i].trim()); 213 } 214 215 p = params.indexOf('?'); 216 if (p >= 0) { 217 if (p > 0) { 218 rc.path = stripPrefix(params.substring(0, p), "/"); 219 } 220 rc.parameters = parseQuery(params.substring(p + 1)); 221 } else { 222 if (params.length() > 0) { 223 rc.path = stripPrefix(params, "/"); 224 } 225 rc.parameters = Collections.EMPTY_MAP; 226 } 227 } 228 229 @SuppressWarnings("unchecked") 230 private static String[] splitComponents(String str) { 231 ArrayList l = new ArrayList(); 232 233 int last = 0; 234 int depth = 0; 235 char chars[] = str.toCharArray(); 236 for (int i = 0; i < chars.length; i++) { 237 switch (chars[i]) { 238 case '(': 239 depth++; 240 break; 241 case ')': 242 depth--; 243 break; 244 case ',': 245 if (depth == 0) { 246 String s = str.substring(last, i); 247 l.add(s); 248 last = i + 1; 249 } 250 break; 251 default: 252 } 253 } 254 255 String s = str.substring(last); 256 if (s.length() != 0) { 257 l.add(s); 258 } 259 260 String rc[] = new String[l.size()]; 261 l.toArray(rc); 262 return rc; 263 } 264 265 public static String stripPrefix(String value, String prefix) { 266 if (value.startsWith(prefix)) { 267 return value.substring(prefix.length()); 268 } 269 return value; 270 } 271 272 public static URI stripScheme(URI uri) throws URISyntaxException { 273 return new URI(stripPrefix(uri.getSchemeSpecificPart().trim(), "//")); 274 } 275 276 public static String createQueryString(Map options) throws URISyntaxException { 277 try { 278 if (options.size() > 0) { 279 StringBuffer rc = new StringBuffer(); 280 boolean first = true; 281 for (Object o : options.keySet()) { 282 if (first) { 283 first = false; 284 } else { 285 rc.append("&"); 286 } 287 288 String key = (String) o; 289 String value = (String) options.get(key); 290 rc.append(URLEncoder.encode(key, CHARSET)); 291 // only append if value is not null 292 if (value != null) { 293 rc.append("="); 294 rc.append(URLEncoder.encode(value, CHARSET)); 295 } 296 } 297 return rc.toString(); 298 } else { 299 return ""; 300 } 301 } catch (UnsupportedEncodingException e) { 302 URISyntaxException se = new URISyntaxException(e.toString(), "Invalid encoding"); 303 se.initCause(e); 304 throw se; 305 } 306 } 307 308 /** 309 * Creates a URI from the original URI and the remaining parameters 310 */ 311 public static URI createRemainingURI(URI originalURI, Map params) throws URISyntaxException { 312 String s = createQueryString(params); 313 if (s.length() == 0) { 314 s = null; 315 } 316 return createURIWithQuery(originalURI, s); 317 } 318 319 public static URI changeScheme(URI bindAddr, String scheme) throws URISyntaxException { 320 return new URI(scheme, bindAddr.getUserInfo(), bindAddr.getHost(), bindAddr.getPort(), bindAddr 321 .getPath(), bindAddr.getQuery(), bindAddr.getFragment()); 322 } 323 324 public static boolean checkParenthesis(String str) { 325 boolean result = true; 326 if (str != null) { 327 int open = 0; 328 int closed = 0; 329 330 int i = 0; 331 while ((i = str.indexOf('(', i)) >= 0) { 332 i++; 333 open++; 334 } 335 i = 0; 336 while ((i = str.indexOf(')', i)) >= 0) { 337 i++; 338 closed++; 339 } 340 result = open == closed; 341 } 342 return result; 343 } 344 345 /** 346 * Normalizes the uri by reordering the parameters so they are sorted and thus 347 * we can use the uris for endpoint matching. 348 * 349 * @param uri the uri 350 * @return the normalized uri 351 * @throws URISyntaxException in thrown if the uri syntax is invalid 352 */ 353 @SuppressWarnings("unchecked") 354 public static String normalizeUri(String uri) throws URISyntaxException { 355 356 URI u = new URI(UnsafeUriCharactersEncoder.encode(uri)); 357 String path = u.getSchemeSpecificPart(); 358 String scheme = u.getScheme(); 359 360 // not possible to normalize 361 if (scheme == null || path == null) { 362 return uri; 363 } 364 365 // lets trim off any query arguments 366 if (path.startsWith("//")) { 367 path = path.substring(2); 368 } 369 int idx = path.indexOf('?'); 370 if (idx > 0) { 371 path = path.substring(0, idx); 372 } 373 374 // in case there are parameters we should reorder them 375 Map parameters = URISupport.parseParameters(u); 376 if (parameters.isEmpty()) { 377 // no parameters then just return 378 return buildUri(scheme, path, null); 379 } else { 380 // reorder parameters a..z 381 List<String> keys = new ArrayList<String>(parameters.keySet()); 382 Collections.sort(keys); 383 384 Map<String, Object> sorted = new LinkedHashMap<String, Object>(parameters.size()); 385 for (String key : keys) { 386 sorted.put(key, parameters.get(key)); 387 } 388 389 // build uri object with sorted parameters 390 String query = URISupport.createQueryString(sorted); 391 return buildUri(scheme, path, query); 392 } 393 } 394 395 private static String buildUri(String scheme, String path, String query) { 396 // must include :// to do a correct URI all components can work with 397 return scheme + "://" + path + (query != null ? "?" + query : ""); 398 } 399 400 }