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 }