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 }