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 }