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    import org.apache.camel.ResolveEndpointFailedException;
031    
032    /**
033     * URI utilities.
034     *
035     * @version $Revision: 896014 $
036     */
037    public final class URISupport {
038    
039        private static final String CHARSET = "UTF-8";
040    
041        private URISupport() {
042            // Helper class
043        }
044    
045        @SuppressWarnings("unchecked")
046        public static Map<String, Object> parseQuery(String uri) throws URISyntaxException {
047            // must check for trailing & as the uri.split("&") will ignore those
048            if (uri != null && uri.endsWith("&")) {
049                throw new URISyntaxException(uri, "Invalid uri syntax: Trailing & marker found. "
050                    + "Check the uri and remove the trailing & marker.");
051            }
052    
053            try {
054                // use a linked map so the parameters is in the same order
055                Map<String, Object> rc = new LinkedHashMap<String, Object>();
056                if (uri != null) {
057                    String[] parameters = uri.split("&");
058                    for (String parameter : parameters) {
059                        int p = parameter.indexOf("=");
060                        if (p >= 0) {
061                            String name = URLDecoder.decode(parameter.substring(0, p), CHARSET);
062                            String value = URLDecoder.decode(parameter.substring(p + 1), CHARSET);
063                            rc.put(name, value);
064                        } else {
065                            rc.put(parameter, null);
066                        }
067                    }
068                }
069                return rc;
070            } catch (UnsupportedEncodingException e) {
071                URISyntaxException se = new URISyntaxException(e.toString(), "Invalid encoding");
072                se.initCause(e);
073                throw se;
074            }
075        }
076    
077        public static Map<String, Object> parseParameters(URI uri) throws URISyntaxException {
078            String query = uri.getQuery();
079            if (query == null) {
080                String schemeSpecificPart = uri.getSchemeSpecificPart();
081                int idx = schemeSpecificPart.lastIndexOf('?');
082                if (idx < 0) {
083                    // return an empty map
084                    return new LinkedHashMap<String, Object>(0);
085                } else {
086                    query = schemeSpecificPart.substring(idx + 1);
087                }
088            } else {
089                query = stripPrefix(query, "?");
090            }
091            return parseQuery(query);
092        }
093    
094        /**
095         * Creates a URI with the given query
096         */
097        public static URI createURIWithQuery(URI uri, String query) throws URISyntaxException {
098            return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(),
099                           query, uri.getFragment());
100        }
101    
102        public static String stripPrefix(String value, String prefix) {
103            if (value.startsWith(prefix)) {
104                return value.substring(prefix.length());
105            }
106            return value;
107        }
108    
109        public static String createQueryString(Map<Object, Object> options) throws URISyntaxException {
110            try {
111                if (options.size() > 0) {
112                    StringBuffer rc = new StringBuffer();
113                    boolean first = true;
114                    for (Object o : options.keySet()) {
115                        if (first) {
116                            first = false;
117                        } else {
118                            rc.append("&");
119                        }
120    
121                        String key = (String) o;
122                        String value = (String) options.get(key);
123                        rc.append(URLEncoder.encode(key, CHARSET));
124                        // only append if value is not null
125                        if (value != null) {
126                            rc.append("=");
127                            rc.append(URLEncoder.encode(value, CHARSET));
128                        }
129                    }
130                    return rc.toString();
131                } else {
132                    return "";
133                }
134            } catch (UnsupportedEncodingException e) {
135                URISyntaxException se = new URISyntaxException(e.toString(), "Invalid encoding");
136                se.initCause(e);
137                throw se;
138            }
139        }
140    
141        /**
142         * Creates a URI from the original URI and the remaining parameters
143         * <p/>
144         * Used by various Camel components
145         */
146        public static URI createRemainingURI(URI originalURI, Map<Object, Object> params) throws URISyntaxException {
147            String s = createQueryString(params);
148            if (s.length() == 0) {
149                s = null;
150            }
151            return createURIWithQuery(originalURI, s);
152        }
153    
154        /**
155         * Normalizes the uri by reordering the parameters so they are sorted and thus
156         * we can use the uris for endpoint matching.
157         *
158         * @param uri the uri
159         * @return the normalized uri
160         * @throws URISyntaxException in thrown if the uri syntax is invalid
161         */
162        @SuppressWarnings("unchecked")
163        public static String normalizeUri(String uri) throws URISyntaxException {
164    
165            URI u = new URI(UnsafeUriCharactersEncoder.encode(uri));
166            String path = u.getSchemeSpecificPart();
167            String scheme = u.getScheme();
168    
169            // not possible to normalize
170            if (scheme == null || path == null) {
171                return uri;
172            }
173    
174            // lets trim off any query arguments
175            if (path.startsWith("//")) {
176                path = path.substring(2);
177            }
178            int idx = path.indexOf('?');
179            if (idx > 0) {
180                path = path.substring(0, idx);
181            }
182    
183            // in case there are parameters we should reorder them
184            Map parameters = URISupport.parseParameters(u);
185            if (parameters.isEmpty()) {
186                // no parameters then just return
187                return buildUri(scheme, path, null);
188            } else {
189                // reorder parameters a..z
190                List<String> keys = new ArrayList<String>(parameters.keySet());
191                Collections.sort(keys);
192    
193                Map<Object, Object> sorted = new LinkedHashMap<Object, Object>(parameters.size());
194                for (String key : keys) {
195                    sorted.put(key, parameters.get(key));
196                }
197    
198                // build uri object with sorted parameters
199                String query = URISupport.createQueryString(sorted);
200                return buildUri(scheme, path, query);
201            }
202        }
203    
204        private static String buildUri(String scheme, String path, String query) {
205            // must include :// to do a correct URI all components can work with
206            return scheme + "://" + path + (query != null ? "?" + query : "");
207        }
208    }