001    /*
002     * Copyright 2003-2008 the original author or authors.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     *
016     * You are receiving this code free of charge, which represents many hours of
017     * effort from other individuals and corporations.  As a responsible member 
018     * of the community, you are asked (but not required) to donate any 
019     * enhancements or improvements back to the community under a similar open 
020     * source license.  Thank you. -TMN
021     */
022    package groovyx.net.http;
023    
024    import java.net.MalformedURLException;
025    import java.net.URI;
026    import java.net.URISyntaxException;
027    import java.net.URL;
028    import java.util.ArrayList;
029    import java.util.HashMap;
030    import java.util.Iterator;
031    import java.util.List;
032    import java.util.Map;
033    
034    import org.apache.http.NameValuePair;
035    import org.apache.http.client.utils.URLEncodedUtils;
036    import org.apache.http.message.BasicNameValuePair;
037    
038    /**
039     * This class implements a mutable URI.  All <code>set</code>, <code>add</code> 
040     * and <code>remove</code> methods affect this class' internal URI 
041     * representation.  All mutator methods support chaining, e.g.
042     * <pre>
043     * new URIBuilder("http://www.google.com/")
044     *   .setScheme( "https" )
045     *   .setPort( 443 )
046     *   .setPath( "some/path" )
047     *   .toString();
048     * </pre>
049     * A slightly more 'Groovy' version would be:
050     * <pre>
051     * new URIBuilder('http://www.google.com/').with {
052     *    scheme = 'https'
053     *    port = 443
054     *    path = 'some/path'
055     *    query = [p1:1, p2:'two']
056     * }.toString()
057     * </pre>
058     * @author <a href='mailto:tnichols@enernoc.com'>Tom Nichols</a>
059     */
060    public class URIBuilder {
061            protected URI base;
062            private final String ENC = "UTF-8"; 
063            
064            public URIBuilder( String url ) throws URISyntaxException {
065                    base = new URI(url);
066            }
067            
068            public URIBuilder( URL url ) throws URISyntaxException {
069                    this.base = url.toURI();
070            }
071            
072            public URIBuilder( URI url ) {
073                    this.base = url;
074            }
075            
076            /**
077             * Attempts to convert a URL or String to a URI.
078             * @param uri a {@link URI}, {@link URL} or any object that produces a 
079             *   parse-able URI string from its <code>toString()</code> result.
080             * @return a valid URI parsed from the given object
081             * @throws URISyntaxException
082             */
083            public static URI convertToURI( Object uri ) throws URISyntaxException {
084                    if ( uri instanceof URI ) ;
085                    else if ( uri instanceof URL ) uri = ((URL)uri).toURI();
086                    else uri = new URI( uri.toString() ); // assume any other object type produces a valid URI string
087                    return (URI)uri;
088            }
089            
090            /**
091             * AKA protocol 
092             * @throws URISyntaxException 
093             */
094            public URIBuilder setScheme( String scheme ) throws URISyntaxException {
095                    this.base = new URI( scheme, base.getUserInfo(), 
096                                    base.getHost(), base.getPort(), base.getPath(),
097                                    base.getQuery(), base.getFragment() );
098                    return this;
099            }
100            
101            public URIBuilder setPort( int port ) throws URISyntaxException {
102                    this.base = new URI( base.getScheme(), base.getUserInfo(), 
103                                    base.getHost(), port, base.getPath(),
104                                    base.getQuery(), base.getFragment() );
105                    return this;
106            }
107            
108            public URIBuilder setHost( String host ) throws URISyntaxException {
109                    this.base = new URI( base.getScheme(), base.getUserInfo(), 
110                                    host, base.getPort(), base.getPath(),
111                                    base.getQuery(), base.getFragment() );
112                    return this;
113            }
114            
115            public URIBuilder setPath( String path ) throws URISyntaxException {
116                    path = base.resolve( path ).getPath();
117                    this.base = new URI( base.getScheme(), base.getUserInfo(), 
118                                    base.getHost(), base.getPort(), path,
119                                    base.getQuery(), base.getFragment() );
120                    return this;
121            }
122            
123            /**
124             * Set the query portion of the URI
125             * @param params a Map of parameters that will be transformed into the query string
126             * @return
127             * @throws URISyntaxException
128             */
129            public URIBuilder setQuery( Map<String,?> params ) throws URISyntaxException {
130                    if ( params == null || params.size() < 1 ) {
131                            this.base = new URI( base.getScheme(), base.getUserInfo(), 
132                                    base.getHost(), base.getPort(), base.getPath(),
133                                    null, base.getFragment() );
134                    }
135                    else {
136                            List<NameValuePair> pairs = new ArrayList<NameValuePair>(params.size());
137                            StringBuilder sb = new StringBuilder();
138                            String path = base.getPath();
139                            if ( path != null ) sb.append( path );
140                            sb.append( '?' );
141                            for ( String key : params.keySet() ) {
142                                    Object val = params.get(key);
143                                    pairs.add( new BasicNameValuePair( key, 
144                                                    ( val != null ) ? val.toString() : "" ) );
145                            }
146                            sb.append( URLEncodedUtils.format( pairs, ENC ) ); 
147                            String frag = base.getFragment();
148                            if ( frag != null ) sb.append( '#' ).append( frag );
149                            this.base = base.resolve( sb.toString() );
150                    }
151                    return this;
152            }
153            
154            /**
155             * Get the query string as a map
156             * @return
157             */
158            public Map<String,String> getQuery() {
159                    Map<String,String> params = new HashMap<String, String>();          
160                    List<NameValuePair> pairs = URLEncodedUtils.parse( this.base, ENC );
161                    for ( NameValuePair pair : pairs ) 
162                            params.put( pair.getName(), pair.getValue() );
163                    return params;
164            }
165            
166            public boolean hasQueryParam( String name ) {
167                    return getQuery().get( name ) != null;
168            }
169            
170            public URIBuilder removeQueryParam( String param ) throws URISyntaxException {
171                    Map<String,String> params = getQuery();
172                    params.remove( param );
173                    this.setQuery( params );
174                    return this;
175            }
176            
177            /**
178             * This will append a param to the existing query string.  If the given 
179             * param is already part of the query string, it will be replaced.
180             * @param param
181             * @param value
182             * @throws URISyntaxException 
183             */
184            public URIBuilder addQueryParam( String param, Object value ) throws URISyntaxException {
185                    Map<String,String> params = getQuery();
186                    if ( value == null ) value = ""; 
187                    params.put( param, value.toString() );
188                    this.setQuery( params );
189                    return this;
190            }
191            
192            @SuppressWarnings("unchecked")
193            public URIBuilder addQueryParams( Map<String,?> params ) throws URISyntaxException {
194                    Map existing = this.getQuery();
195                    existing.putAll( params );
196                    this.setQuery( existing );
197                    return this;
198            }
199            
200            /**
201             * The document fragment, without a preceeding '#'
202             * @param fragment
203             * @throws URISyntaxException
204             */
205            public URIBuilder setFragment( String fragment ) throws URISyntaxException {
206                    this.base = new URI( base.getScheme(), base.getUserInfo(), 
207                                    base.getHost(), base.getPort(), base.getPath(),
208                                    base.getQuery(), fragment );
209                    return this;
210            }
211            
212            @Override public String toString() {
213                    return base.toString();
214            }
215            
216            public URL toURL() throws MalformedURLException {
217                    return base.toURL();
218            }
219            
220            public URI toURI() { return this.base; }
221    }