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.runtimecatalog; 018 019import java.io.UnsupportedEncodingException; 020import java.net.URI; 021import java.net.URISyntaxException; 022import java.net.URLDecoder; 023import java.net.URLEncoder; 024import java.util.ArrayList; 025import java.util.Iterator; 026import java.util.LinkedHashMap; 027import java.util.List; 028import java.util.Map; 029 030/** 031 * Copied from org.apache.camel.util.URISupport 032 */ 033public final class URISupport { 034 035 public static final String RAW_TOKEN_START = "RAW("; 036 public static final String RAW_TOKEN_END = ")"; 037 038 private static final String CHARSET = "UTF-8"; 039 040 private URISupport() { 041 // Helper class 042 } 043 044 /** 045 * Normalizes the URI so unsafe characters is encoded 046 * 047 * @param uri the input uri 048 * @return as URI instance 049 * @throws URISyntaxException is thrown if syntax error in the input uri 050 */ 051 public static URI normalizeUri(String uri) throws URISyntaxException { 052 return new URI(UnsafeUriCharactersEncoder.encode(uri, true)); 053 } 054 055 public static Map<String, Object> extractProperties(Map<String, Object> properties, String optionPrefix) { 056 Map<String, Object> rc = new LinkedHashMap<String, Object>(properties.size()); 057 058 for (Iterator<Map.Entry<String, Object>> it = properties.entrySet().iterator(); it.hasNext();) { 059 Map.Entry<String, Object> entry = it.next(); 060 String name = entry.getKey(); 061 if (name.startsWith(optionPrefix)) { 062 Object value = properties.get(name); 063 name = name.substring(optionPrefix.length()); 064 rc.put(name, value); 065 it.remove(); 066 } 067 } 068 069 return rc; 070 } 071 072 /** 073 * Strips the query parameters from the uri 074 * 075 * @param uri the uri 076 * @return the uri without the query parameter 077 */ 078 public static String stripQuery(String uri) { 079 int idx = uri.indexOf('?'); 080 if (idx > -1) { 081 uri = uri.substring(0, idx); 082 } 083 return uri; 084 } 085 086 /** 087 * Parses the query parameters of the uri (eg the query part). 088 * 089 * @param uri the uri 090 * @return the parameters, or an empty map if no parameters (eg never null) 091 * @throws URISyntaxException is thrown if uri has invalid syntax. 092 */ 093 public static Map<String, Object> parseParameters(URI uri) throws URISyntaxException { 094 String query = uri.getQuery(); 095 if (query == null) { 096 String schemeSpecificPart = uri.getSchemeSpecificPart(); 097 int idx = schemeSpecificPart.indexOf('?'); 098 if (idx < 0) { 099 // return an empty map 100 return new LinkedHashMap<String, Object>(0); 101 } else { 102 query = schemeSpecificPart.substring(idx + 1); 103 } 104 } else { 105 query = stripPrefix(query, "?"); 106 } 107 return parseQuery(query); 108 } 109 110 /** 111 * Strips the prefix from the value. 112 * <p/> 113 * Returns the value as-is if not starting with the prefix. 114 * 115 * @param value the value 116 * @param prefix the prefix to remove from value 117 * @return the value without the prefix 118 */ 119 public static String stripPrefix(String value, String prefix) { 120 if (value != null && value.startsWith(prefix)) { 121 return value.substring(prefix.length()); 122 } 123 return value; 124 } 125 126 /** 127 * Parses the query part of the uri (eg the parameters). 128 * <p/> 129 * The URI parameters will by default be URI encoded. However you can define a parameter 130 * values with the syntax: <tt>key=RAW(value)</tt> which tells Camel to not encode the value, 131 * and use the value as is (eg key=value) and the value has <b>not</b> been encoded. 132 * 133 * @param uri the uri 134 * @return the parameters, or an empty map if no parameters (eg never null) 135 * @throws URISyntaxException is thrown if uri has invalid syntax. 136 * @see #RAW_TOKEN_START 137 * @see #RAW_TOKEN_END 138 */ 139 public static Map<String, Object> parseQuery(String uri) throws URISyntaxException { 140 return parseQuery(uri, false); 141 } 142 143 /** 144 * Parses the query part of the uri (eg the parameters). 145 * <p/> 146 * The URI parameters will by default be URI encoded. However you can define a parameter 147 * values with the syntax: <tt>key=RAW(value)</tt> which tells Camel to not encode the value, 148 * and use the value as is (eg key=value) and the value has <b>not</b> been encoded. 149 * 150 * @param uri the uri 151 * @param useRaw whether to force using raw values 152 * @return the parameters, or an empty map if no parameters (eg never null) 153 * @throws URISyntaxException is thrown if uri has invalid syntax. 154 * @see #RAW_TOKEN_START 155 * @see #RAW_TOKEN_END 156 */ 157 public static Map<String, Object> parseQuery(String uri, boolean useRaw) throws URISyntaxException { 158 // must check for trailing & as the uri.split("&") will ignore those 159 if (uri != null && uri.endsWith("&")) { 160 throw new URISyntaxException(uri, "Invalid uri syntax: Trailing & marker found. " 161 + "Check the uri and remove the trailing & marker."); 162 } 163 164 if (isEmpty(uri)) { 165 // return an empty map 166 return new LinkedHashMap<String, Object>(0); 167 } 168 169 // need to parse the uri query parameters manually as we cannot rely on splitting by &, 170 // as & can be used in a parameter value as well. 171 172 try { 173 // use a linked map so the parameters is in the same order 174 Map<String, Object> rc = new LinkedHashMap<String, Object>(); 175 176 boolean isKey = true; 177 boolean isValue = false; 178 boolean isRaw = false; 179 StringBuilder key = new StringBuilder(); 180 StringBuilder value = new StringBuilder(); 181 182 // parse the uri parameters char by char 183 for (int i = 0; i < uri.length(); i++) { 184 // current char 185 char ch = uri.charAt(i); 186 // look ahead of the next char 187 char next; 188 if (i <= uri.length() - 2) { 189 next = uri.charAt(i + 1); 190 } else { 191 next = '\u0000'; 192 } 193 194 // are we a raw value 195 isRaw = value.toString().startsWith(RAW_TOKEN_START); 196 197 // if we are in raw mode, then we keep adding until we hit the end marker 198 if (isRaw) { 199 if (isKey) { 200 key.append(ch); 201 } else if (isValue) { 202 value.append(ch); 203 } 204 205 // we only end the raw marker if its )& or at the end of the value 206 207 boolean end = ch == RAW_TOKEN_END.charAt(0) && (next == '&' || next == '\u0000'); 208 if (end) { 209 // raw value end, so add that as a parameter, and reset flags 210 addParameter(key.toString(), value.toString(), rc, useRaw || isRaw); 211 key.setLength(0); 212 value.setLength(0); 213 isKey = true; 214 isValue = false; 215 isRaw = false; 216 // skip to next as we are in raw mode and have already added the value 217 i++; 218 } 219 continue; 220 } 221 222 // if its a key and there is a = sign then the key ends and we are in value mode 223 if (isKey && ch == '=') { 224 isKey = false; 225 isValue = true; 226 isRaw = false; 227 continue; 228 } 229 230 // the & denote parameter is ended 231 if (ch == '&') { 232 // parameter is ended, as we hit & separator 233 String aKey = key.toString(); 234 // the key may be a placeholder of options which we then do not know what is 235 boolean validKey = !aKey.startsWith("{{") && !aKey.endsWith("}}"); 236 if (validKey) { 237 addParameter(aKey, value.toString(), rc, useRaw || isRaw); 238 } 239 key.setLength(0); 240 value.setLength(0); 241 isKey = true; 242 isValue = false; 243 isRaw = false; 244 continue; 245 } 246 247 // regular char so add it to the key or value 248 if (isKey) { 249 key.append(ch); 250 } else if (isValue) { 251 value.append(ch); 252 } 253 } 254 255 // any left over parameters, then add that 256 if (key.length() > 0) { 257 String aKey = key.toString(); 258 // the key may be a placeholder of options which we then do not know what is 259 boolean validKey = !aKey.startsWith("{{") && !aKey.endsWith("}}"); 260 if (validKey) { 261 addParameter(aKey, value.toString(), rc, useRaw || isRaw); 262 } 263 } 264 265 return rc; 266 267 } catch (UnsupportedEncodingException e) { 268 URISyntaxException se = new URISyntaxException(e.toString(), "Invalid encoding"); 269 se.initCause(e); 270 throw se; 271 } 272 } 273 274 @SuppressWarnings("unchecked") 275 private static void addParameter(String name, String value, Map<String, Object> map, boolean isRaw) throws UnsupportedEncodingException { 276 name = URLDecoder.decode(name, CHARSET); 277 if (!isRaw) { 278 // need to replace % with %25 279 value = URLDecoder.decode(value.replaceAll("%", "%25"), CHARSET); 280 } 281 282 // does the key already exist? 283 if (map.containsKey(name)) { 284 // yes it does, so make sure we can support multiple values, but using a list 285 // to hold the multiple values 286 Object existing = map.get(name); 287 List<String> list; 288 if (existing instanceof List) { 289 list = (List<String>) existing; 290 } else { 291 // create a new list to hold the multiple values 292 list = new ArrayList<String>(); 293 String s = existing != null ? existing.toString() : null; 294 if (s != null) { 295 list.add(s); 296 } 297 } 298 list.add(value); 299 map.put(name, list); 300 } else { 301 map.put(name, value); 302 } 303 } 304 305 /** 306 * Assembles a query from the given map. 307 * 308 * @param options the map with the options (eg key/value pairs) 309 * @param ampersand to use & for Java code, and & for XML 310 * @return a query string with <tt>key1=value&key2=value2&...</tt>, or an empty string if there is no options. 311 * @throws URISyntaxException is thrown if uri has invalid syntax. 312 */ 313 public static String createQueryString(Map<String, String> options, String ampersand, boolean encode) throws URISyntaxException { 314 try { 315 if (options.size() > 0) { 316 StringBuilder rc = new StringBuilder(); 317 boolean first = true; 318 for (Object o : options.keySet()) { 319 if (first) { 320 first = false; 321 } else { 322 rc.append(ampersand); 323 } 324 325 String key = (String) o; 326 Object value = options.get(key); 327 328 // use the value as a String 329 String s = value != null ? value.toString() : null; 330 appendQueryStringParameter(key, s, rc, encode); 331 } 332 return rc.toString(); 333 } else { 334 return ""; 335 } 336 } catch (UnsupportedEncodingException e) { 337 URISyntaxException se = new URISyntaxException(e.toString(), "Invalid encoding"); 338 se.initCause(e); 339 throw se; 340 } 341 } 342 343 private static void appendQueryStringParameter(String key, String value, StringBuilder rc, boolean encode) throws UnsupportedEncodingException { 344 if (encode) { 345 rc.append(URLEncoder.encode(key, CHARSET)); 346 } else { 347 rc.append(key); 348 } 349 // only append if value is not null 350 if (value != null) { 351 rc.append("="); 352 if (value.startsWith(RAW_TOKEN_START) && value.endsWith(RAW_TOKEN_END)) { 353 // do not encode RAW parameters 354 rc.append(value); 355 } else { 356 if (encode) { 357 rc.append(URLEncoder.encode(value, CHARSET)); 358 } else { 359 rc.append(value); 360 } 361 } 362 } 363 } 364 365 /** 366 * Tests whether the value is <tt>null</tt> or an empty string. 367 * 368 * @param value the value, if its a String it will be tested for text length as well 369 * @return true if empty 370 */ 371 public static boolean isEmpty(Object value) { 372 return !isNotEmpty(value); 373 } 374 375 /** 376 * Tests whether the value is <b>not</b> <tt>null</tt> or an empty string. 377 * 378 * @param value the value, if its a String it will be tested for text length as well 379 * @return true if <b>not</b> empty 380 */ 381 public static boolean isNotEmpty(Object value) { 382 if (value == null) { 383 return false; 384 } else if (value instanceof String) { 385 String text = (String) value; 386 return text.trim().length() > 0; 387 } else { 388 return true; 389 } 390 } 391 392}