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