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.activemq.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.HashMap;
027 import java.util.List;
028 import java.util.Map;
029
030 /**
031 * Utility class that provides methods for parsing URI's
032 *
033 * This class can be used to split composite URI's into their component parts and is used to extract any
034 * URI options from each URI in order to set specific properties on Beans.
035 */
036 public class URISupport {
037
038 /**
039 * A composite URI can be split into one or more CompositeData object which each represent the
040 * individual URIs that comprise the composite one.
041 */
042 public static class CompositeData {
043 private String host;
044 private String scheme;
045 private String path;
046 private URI components[];
047 private Map<String, String> parameters;
048 private String fragment;
049
050 public URI[] getComponents() {
051 return components;
052 }
053
054 public String getFragment() {
055 return fragment;
056 }
057
058 public Map<String, String> getParameters() {
059 return parameters;
060 }
061
062 public String getScheme() {
063 return scheme;
064 }
065
066 public String getPath() {
067 return path;
068 }
069
070 public String getHost() {
071 return host;
072 }
073
074 public URI toURI() throws URISyntaxException {
075 StringBuffer sb = new StringBuffer();
076 if (scheme != null) {
077 sb.append(scheme);
078 sb.append(':');
079 }
080
081 if (host != null && host.length() != 0) {
082 sb.append(host);
083 } else {
084 sb.append('(');
085 for (int i = 0; i < components.length; i++) {
086 if (i != 0) {
087 sb.append(',');
088 }
089 sb.append(components[i].toString());
090 }
091 sb.append(')');
092 }
093
094 if (path != null) {
095 sb.append('/');
096 sb.append(path);
097 }
098 if (!parameters.isEmpty()) {
099 sb.append("?");
100 sb.append(createQueryString(parameters));
101 }
102 if (fragment != null) {
103 sb.append("#");
104 sb.append(fragment);
105 }
106 return new URI(sb.toString());
107 }
108 }
109
110 /**
111 * Give a URI break off any URI options and store them in a Key / Value Mapping.
112 *
113 * @param uri
114 * The URI whose query should be extracted and processed.
115 *
116 * @return A Mapping of the URI options.
117 * @throws URISyntaxException
118 */
119 public static Map<String, String> parseQuery(String uri) throws URISyntaxException {
120 try {
121 uri = uri.substring(uri.lastIndexOf("?") + 1); // get only the relevant part of the query
122 Map<String, String> rc = new HashMap<String, String>();
123 if (uri != null && !uri.isEmpty()) {
124 String[] parameters = uri.split("&");
125 for (int i = 0; i < parameters.length; i++) {
126 int p = parameters[i].indexOf("=");
127 if (p >= 0) {
128 String name = URLDecoder.decode(parameters[i].substring(0, p), "UTF-8");
129 String value = URLDecoder.decode(parameters[i].substring(p + 1), "UTF-8");
130 rc.put(name, value);
131 } else {
132 rc.put(parameters[i], null);
133 }
134 }
135 }
136 return rc;
137 } catch (UnsupportedEncodingException e) {
138 throw (URISyntaxException)new URISyntaxException(e.toString(), "Invalid encoding").initCause(e);
139 }
140 }
141
142 /**
143 * Given a URI parse and extract any URI query options and return them as a Key / Value mapping.
144 *
145 * This method differs from the {@link parseQuery} method in that it handles composite URI types and
146 * will extract the URI options from the outermost composite URI.
147 *
148 * @param uri
149 * The URI whose query should be extracted and processed.
150 *
151 * @return A Mapping of the URI options.
152 * @throws URISyntaxException
153 */
154 public static Map<String, String> parseParameters(URI uri) throws URISyntaxException {
155 if (!isCompositeURI(uri)) {
156 return uri.getQuery() == null ? emptyMap() : parseQuery(stripPrefix(uri.getQuery(), "?"));
157 } else {
158 CompositeData data = URISupport.parseComposite(uri);
159 Map<String, String> parameters = new HashMap<String, String>();
160 parameters.putAll(data.getParameters());
161 if (parameters.isEmpty()) {
162 parameters = emptyMap();
163 }
164
165 return parameters;
166 }
167 }
168
169 /**
170 * Given a Key / Value mapping create and append a URI query value that represents the mapped entries, return the
171 * newly updated URI that contains the value of the given URI and the appended query value.
172 *
173 * @param uri
174 * The source URI that will have the Map entries appended as a URI query value.
175 * @param queryParameters
176 * The Key / Value mapping that will be transformed into a URI query string.
177 *
178 * @return A new URI value that combines the given URI and the constructed query string.
179 * @throws URISyntaxException
180 */
181 public static URI applyParameters(URI uri, Map<String, String> queryParameters) throws URISyntaxException {
182 return applyParameters(uri, queryParameters, "");
183 }
184
185 /**
186 * Given a Key / Value mapping create and append a URI query value that represents the mapped entries, return the
187 * newly updated URI that contains the value of the given URI and the appended query value. Each entry in the query
188 * string is prefixed by the supplied optionPrefix string.
189 *
190 * @param uri
191 * The source URI that will have the Map entries appended as a URI query value.
192 * @param queryParameters
193 * The Key / Value mapping that will be transformed into a URI query string.
194 * @param optionPrefix
195 * A string value that when not null or empty is used to prefix each query option key.
196 *
197 * @return A new URI value that combines the given URI and the constructed query string.
198 * @throws URISyntaxException
199 */
200 public static URI applyParameters(URI uri, Map<String, String> queryParameters, String optionPrefix) throws URISyntaxException {
201 if (queryParameters != null && !queryParameters.isEmpty()) {
202 StringBuffer newQuery = uri.getRawQuery() != null ? new StringBuffer(uri.getRawQuery()) : new StringBuffer() ;
203 for ( Map.Entry<String, String> param: queryParameters.entrySet()) {
204 if (param.getKey().startsWith(optionPrefix)) {
205 if (newQuery.length()!=0) {
206 newQuery.append('&');
207 }
208 final String key = param.getKey().substring(optionPrefix.length());
209 newQuery.append(key).append('=').append(param.getValue());
210 }
211 }
212 uri = createURIWithQuery(uri, newQuery.toString());
213 }
214 return uri;
215 }
216
217 @SuppressWarnings("unchecked")
218 private static Map<String, String> emptyMap() {
219 return Collections.EMPTY_MAP;
220 }
221
222 /**
223 * Removes any URI query from the given uri and return a new URI that does not contain the query portion.
224 *
225 * @param uri
226 * The URI whose query value is to be removed.
227 *
228 * @return a new URI that does not contain a query value.
229 * @throws URISyntaxException
230 */
231 public static URI removeQuery(URI uri) throws URISyntaxException {
232 return createURIWithQuery(uri, null);
233 }
234
235 /**
236 * Creates a URI with the given query, removing an previous query value from the given URI.
237 *
238 * @param uri
239 * The source URI whose existing query is replaced with the newly supplied one.
240 * @param query
241 * The new URI query string that should be appended to the given URI.
242 *
243 * @return a new URI that is a combination of the original URI and the given query string.
244 * @throws URISyntaxException
245 */
246 public static URI createURIWithQuery(URI uri, String query) throws URISyntaxException {
247 String schemeSpecificPart = uri.getRawSchemeSpecificPart();
248 // strip existing query if any
249 int questionMark = schemeSpecificPart.lastIndexOf("?");
250 // make sure question mark is not within parentheses
251 if (questionMark < schemeSpecificPart.lastIndexOf(")")) {
252 questionMark = -1;
253 }
254 if (questionMark > 0) {
255 schemeSpecificPart = schemeSpecificPart.substring(0, questionMark);
256 }
257 if (query != null && query.length() > 0) {
258 schemeSpecificPart += "?" + query;
259 }
260 return new URI(uri.getScheme(), schemeSpecificPart, uri.getFragment());
261 }
262
263 /**
264 * Given a composite URI, parse the individual URI elements contained within that URI and return
265 * a CompsoteData instance that contains the parsed URI values.
266 *
267 * @param uri
268 * The target URI that should be parsed.
269 *
270 * @return a new CompsiteData instance representing the parsed composite URI.
271 * @throws URISyntaxException
272 */
273 public static CompositeData parseComposite(URI uri) throws URISyntaxException {
274
275 CompositeData rc = new CompositeData();
276 rc.scheme = uri.getScheme();
277 String ssp = stripPrefix(uri.getRawSchemeSpecificPart().trim(), "//").trim();
278
279 parseComposite(uri, rc, ssp);
280
281 rc.fragment = uri.getFragment();
282 return rc;
283 }
284
285 /**
286 * Examine a URI and determine if it is a Composite type or not.
287 *
288 * @param uri
289 * The URI that is to be examined.
290 *
291 * @return true if the given URI is a Compsote type.
292 */
293 public static boolean isCompositeURI(URI uri) {
294 String ssp = stripPrefix(uri.getRawSchemeSpecificPart().trim(), "//").trim();
295
296 if (ssp.indexOf('(') == 0 && checkParenthesis(ssp)) {
297 return true;
298 }
299 return false;
300 }
301
302 /**
303 * Given a string and a position in that string of an open parend, find the matching close parend.
304 *
305 * @param str
306 * The string to be searched for a matching parend.
307 * @param first
308 * The index in the string of the opening parend whose close value is to be searched.
309 *
310 * @return the index in the string where the closing parend is located.
311 * @throws URISyntaxException fi the string does not contain a matching parend.
312 */
313 public static int indexOfParenthesisMatch(String str, int first) throws URISyntaxException {
314 int index = -1;
315
316 if (first < 0 || first > str.length()) {
317 throw new IllegalArgumentException("Invalid position for first parenthesis: " + first);
318 }
319
320 if (str.charAt(first) != '(') {
321 throw new IllegalArgumentException("character at indicated position is not a parenthesis");
322 }
323
324 int depth = 1;
325 char[] array = str.toCharArray();
326 for (index = first + 1; index < array.length; ++index) {
327 char current = array[index];
328 if (current == '(') {
329 depth++;
330 } else if (current == ')') {
331 if (--depth == 0) {
332 break;
333 }
334 }
335 }
336
337 if (depth != 0) {
338 throw new URISyntaxException(str, "URI did not contain a matching parenthesis.");
339 }
340
341 return index;
342 }
343
344 /**
345 * Given a composite URI and a CompositeData instance and the scheme specific part extracted from the source URI,
346 * parse the composite URI and populate the CompositeData object with the results. The source URI is used only
347 * for logging as the ssp should have already been extracted from it and passed here.
348 *
349 * @param uri
350 * The original source URI whose ssp is parsed into the composite data.
351 * @param rc
352 * The CompsositeData instance that will be populated from the given ssp.
353 * @param ssp
354 * The scheme specific part from the original string that is a composite or one or more URIs.
355 *
356 * @throws URISyntaxException
357 */
358 private static void parseComposite(URI uri, CompositeData rc, String ssp) throws URISyntaxException {
359 String componentString;
360 String params;
361
362 if (!checkParenthesis(ssp)) {
363 throw new URISyntaxException(uri.toString(), "Not a matching number of '(' and ')' parenthesis");
364 }
365
366 int p;
367 int initialParen = ssp.indexOf("(");
368 if (initialParen == 0) {
369
370 rc.host = ssp.substring(0, initialParen);
371 p = rc.host.indexOf("/");
372
373 if (p >= 0) {
374 rc.path = rc.host.substring(p);
375 rc.host = rc.host.substring(0, p);
376 }
377
378 p = indexOfParenthesisMatch(ssp, initialParen);
379 componentString = ssp.substring(initialParen + 1, p);
380 params = ssp.substring(p + 1).trim();
381
382 } else {
383 componentString = ssp;
384 params = "";
385 }
386
387 String components[] = splitComponents(componentString);
388 rc.components = new URI[components.length];
389 for (int i = 0; i < components.length; i++) {
390 rc.components[i] = new URI(components[i].trim());
391 }
392
393 p = params.indexOf("?");
394 if (p >= 0) {
395 if (p > 0) {
396 rc.path = stripPrefix(params.substring(0, p), "/");
397 }
398 rc.parameters = parseQuery(params.substring(p + 1));
399 } else {
400 if (params.length() > 0) {
401 rc.path = stripPrefix(params, "/");
402 }
403 rc.parameters = emptyMap();
404 }
405 }
406
407 /**
408 * Given the inner portion of a composite URI, split and return each inner URI as a string
409 * element in a new String array.
410 *
411 * @param str
412 * The inner URI elements of a composite URI string.
413 *
414 * @return an array containing each inner URI from the composite one.
415 */
416 private static String[] splitComponents(String str) {
417 List<String> l = new ArrayList<String>();
418
419 int last = 0;
420 int depth = 0;
421 char chars[] = str.toCharArray();
422 for (int i = 0; i < chars.length; i++) {
423 switch (chars[i]) {
424 case '(':
425 depth++;
426 break;
427 case ')':
428 depth--;
429 break;
430 case ',':
431 if (depth == 0) {
432 String s = str.substring(last, i);
433 l.add(s);
434 last = i + 1;
435 }
436 break;
437 default:
438 }
439 }
440
441 String s = str.substring(last);
442 if (s.length() != 0) {
443 l.add(s);
444 }
445
446 String rc[] = new String[l.size()];
447 l.toArray(rc);
448 return rc;
449 }
450
451 /**
452 * String the given prefix from the target string and return the result.
453 *
454 * @param value
455 * The string that should be trimmed of the given prefix if present.
456 * @param prefix
457 * The prefix to remove from the target string.
458 *
459 * @return either the original string or a new string minus the supplied prefix if present.
460 */
461 public static String stripPrefix(String value, String prefix) {
462 if (value.startsWith(prefix)) {
463 return value.substring(prefix.length());
464 }
465 return value;
466 }
467
468 /**
469 * Strip a URI of its scheme element.
470 *
471 * @param uri
472 * The URI whose scheme value should be stripped.
473 *
474 * @return The stripped URI value.
475 * @throws URISyntaxException
476 */
477 public static URI stripScheme(URI uri) throws URISyntaxException {
478 return new URI(stripPrefix(uri.getSchemeSpecificPart().trim(), "//"));
479 }
480
481 /**
482 * Given a key / value mapping, create and return a URI formatted query string that is valid and
483 * can be appended to a URI.
484 *
485 * @param options
486 * The Mapping that will create the new Query string.
487 *
488 * @return a URI formatted query string.
489 * @throws URISyntaxException
490 */
491 public static String createQueryString(Map<String, String> options) throws URISyntaxException {
492 try {
493 if (options.size() > 0) {
494 StringBuffer rc = new StringBuffer();
495 boolean first = true;
496 for (String key : options.keySet()) {
497 if (first) {
498 first = false;
499 } else {
500 rc.append("&");
501 }
502 String value = (String)options.get(key);
503 rc.append(URLEncoder.encode(key, "UTF-8"));
504 rc.append("=");
505 rc.append(URLEncoder.encode(value, "UTF-8"));
506 }
507 return rc.toString();
508 } else {
509 return "";
510 }
511 } catch (UnsupportedEncodingException e) {
512 throw (URISyntaxException)new URISyntaxException(e.toString(), "Invalid encoding").initCause(e);
513 }
514 }
515
516 /**
517 * Creates a URI from the original URI and the remaining parameters.
518 *
519 * When the query options of a URI are applied to certain objects the used portion of the query options needs
520 * to be removed and replaced with those that remain so that other parts of the code can attempt to apply the
521 * remainder or give an error is unknown values were given. This method is used to update a URI with those
522 * remainder values.
523 *
524 * @param originalURI
525 * The URI whose current parameters are remove and replaced with the given remainder value.
526 * @param params
527 * The URI params that should be used to replace the current ones in the target.
528 *
529 * @return a new URI that matches the original one but has its query options replaced with the given ones.
530 * @throws URISyntaxException
531 */
532 public static URI createRemainingURI(URI originalURI, Map<String, String> params) throws URISyntaxException {
533 String s = createQueryString(params);
534 if (s.length() == 0) {
535 s = null;
536 }
537 return createURIWithQuery(originalURI, s);
538 }
539
540 /**
541 * Given a URI value create and return a new URI that matches the target one but with the scheme value
542 * supplied to this method.
543 *
544 * @param bindAddr
545 * The URI whose scheme value should be altered.
546 * @param scheme
547 * The new scheme value to use for the returned URI.
548 *
549 * @return a new URI that is a copy of the original except that its scheme matches the supplied one.
550 * @throws URISyntaxException
551 */
552 public static URI changeScheme(URI bindAddr, String scheme) throws URISyntaxException {
553 return new URI(scheme, bindAddr.getUserInfo(), bindAddr.getHost(), bindAddr.getPort(), bindAddr
554 .getPath(), bindAddr.getQuery(), bindAddr.getFragment());
555 }
556
557 /**
558 * Examine the supplied string and ensure that all parends appear as matching pairs.
559 *
560 * @param str
561 * The target string to examine.
562 *
563 * @return true if the target string has valid parend pairings.
564 */
565 public static boolean checkParenthesis(String str) {
566 boolean result = true;
567 if (str != null) {
568 int open = 0;
569 int closed = 0;
570
571 int i = 0;
572 while ((i = str.indexOf('(', i)) >= 0) {
573 i++;
574 open++;
575 }
576 i = 0;
577 while ((i = str.indexOf(')', i)) >= 0) {
578 i++;
579 closed++;
580 }
581 result = open == closed;
582 }
583 return result;
584 }
585 }