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