001    /*
002     * Copyright 2008-2011 Thomas Nichols.  http://blog.thomnichols.org
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 encouraged (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.io.IOException;
025    import java.net.URISyntaxException;
026    import java.util.Map;
027    
028    import org.apache.http.HttpResponse;
029    import org.apache.http.client.ClientProtocolException;
030    import org.apache.http.client.methods.HttpDelete;
031    import org.apache.http.client.methods.HttpGet;
032    import org.apache.http.client.methods.HttpHead;
033    import org.apache.http.client.methods.HttpOptions;
034    import org.apache.http.client.methods.HttpPost;
035    import org.apache.http.client.methods.HttpPut;
036    
037    /**
038     * Extension to HTTPBuilder that basically attempts to provide a slightly more
039     * REST-ful face on top of HTTPBuilder.  The differences between this class
040     * and HTTPBuilder are such:
041     * 
042     * <ul>
043     *   <li>Access to response headers.  All "request" methods on this class by 
044     *   default return an instance of {@link HttpResponseDecorator}, which allows for simple
045     *   evaluation of the response.</li>
046     *   <li>No streaming responses.  Responses are expected to either not carry data 
047     * (in the case of HEAD or DELETE) or be parse-able into some sort of object.  
048     *   That object is accessible via {@link HttpResponseDecorator#getData()}.</li>
049     * </ul>  
050     * 
051     * <p>By default, all request method methods will return a {@link HttpResponseDecorator}
052     * instance, which provides convenient access to response headers and the parsed
053     * response body.  The response body is parsed based on content-type, identical
054     * to how HTTPBuilder's {@link HTTPBuilder#defaultSuccessHandler(HttpResponseDecorator, 
055     * Object) default response handler} functions.</p>
056     * 
057     * <p>Failed requests (i.e. responses which return a status code &gt; 399) will
058     * by default throw a {@link HttpResponseException}.  This exception may be used 
059     * to retrieve additional information regarding the response as well.</p>
060     * 
061     * @author <a href='mailto:tomstrummer+httpbuilder@gmail.com'>Tom Nichols</a>
062     * @since 0.5
063     */
064    public class RESTClient extends HTTPBuilder {
065            
066    
067            /**
068             * Constructor.
069             * @see HTTPBuilder#HTTPBuilder()
070             */
071            public RESTClient() { super(); }
072            
073            /**
074             * See {@link HTTPBuilder#HTTPBuilder(Object)}
075             * @param defaultURI default request URI (String, URI, URL or {@link URIBuilder})
076             * @throws URISyntaxException
077             */
078            public RESTClient( Object defaultURI ) throws URISyntaxException {
079                    super( defaultURI );
080            }
081            
082            /**
083             * See {@link HTTPBuilder#HTTPBuilder(Object, Object)}
084             * @param defaultURI default request URI (String, URI, URL or {@link URIBuilder})
085             * @param defaultContentType default content-type (String or {@link ContentType})
086             * @throws URISyntaxException
087             */
088            public RESTClient( Object defaultURI, Object defaultContentType ) throws URISyntaxException {
089                    super( defaultURI, defaultContentType );
090            }
091            
092            
093            /**
094             * <p>Convenience method to perform an HTTP GET request.  It will use the HTTPBuilder's
095             * {@link #getHandler() registered response handlers} to handle success or 
096             * failure status codes.  By default, the 
097             * {@link #defaultSuccessHandler(HttpResponseDecorator, Object)}
098             * <code>success</code> response handler will return a decorated response
099             * object that can be used to read response headers and data.</p>
100             * 
101             * <p>A 'failed' response (i.e. any HTTP status code > 399) will be handled 
102             * by the registered 'failure' handler.  
103             * The {@link #defaultFailureHandler(HttpResponseDecorator, Object) 
104             * default failure handler} throws a {@link HttpResponseException}.</p>  
105             * 
106             * @see #defaultSuccessHandler(HttpResponseDecorator, Object)
107             * @see #defaultFailureHandler(HttpResponseDecorator, Object)
108             * @param args named parameters - see 
109             *      {@link HTTPBuilder.RequestConfigDelegate#setPropertiesFromMap(Map)}
110             * @return a {@link HttpResponseDecorator}, unless the default success 
111             *              handler is overridden.
112             * @throws URISyntaxException 
113             * @throws IOException 
114             * @throws ClientProtocolException 
115             */
116            public Object get( Map<String,?> args ) throws ClientProtocolException, 
117                            IOException, URISyntaxException {
118                    return super.doRequest( new RequestConfigDelegate( args, new HttpGet(), null ) );
119            }
120            
121            /** 
122             * <p>Convenience method to perform a POST request.</p>
123             * 
124             * <p>The request body (specified by a <code>body</code> named parameter) 
125             * will be encoded based on the <code>requestContentType</code> named 
126             * parameter, or if none is given, the default 
127             * {@link HTTPBuilder#setContentType(Object) content-type} for this instance.
128             * </p>
129             * 
130             * @param args named parameters - see 
131             *      {@link HTTPBuilder.RequestConfigDelegate#setPropertiesFromMap(Map)}
132             * @return a {@link HttpResponseDecorator}, unless the default success 
133             *              handler is overridden.
134             * @throws ClientProtocolException
135             * @throws IOException
136             * @throws URISyntaxException
137             */
138            @Override public Object post( Map<String,?> args ) 
139                            throws URISyntaxException, ClientProtocolException, IOException {
140                    return super.doRequest( new RequestConfigDelegate( args, new HttpPost(), null ) );
141            }
142            
143            /** 
144             * <p> Convenience method to perform a PUT request.</p>   
145             * 
146             * <p>The request body (specified by a <code>body</code> named parameter) 
147             * will be encoded based on the <code>requestContentType</code> named 
148             * parameter, or if none is given, the default 
149             * {@link HTTPBuilder#setContentType(Object) content-type} for this instance.
150             * </p>
151             * 
152             * @param args named parameters - see 
153             *      {@link HTTPBuilder.RequestConfigDelegate#setPropertiesFromMap(Map)}
154             * @return a {@link HttpResponseDecorator}, unless the default success 
155             *              handler is overridden.
156             * @throws ClientProtocolException
157             * @throws IOException
158             * @throws URISyntaxException
159             */
160            public Object put( Map<String,?> args ) throws URISyntaxException, 
161                            ClientProtocolException, IOException {
162                    return this.doRequest( new RequestConfigDelegate( args, new HttpPut(), null ) );
163            }
164            
165            /** 
166             * <p>Perform a HEAD request, often used to check preconditions before 
167             * sending a large PUT or POST request.</p>
168             * 
169             * @param args named parameters - see 
170             *      {@link HTTPBuilder.RequestConfigDelegate#setPropertiesFromMap(Map)}
171             * @return a {@link HttpResponseDecorator}, unless the default success 
172             *              handler is overridden.
173             * @throws ClientProtocolException
174             * @throws IOException
175             * @throws URISyntaxException
176             */
177            public Object head( Map<String,?> args ) throws URISyntaxException, 
178                            ClientProtocolException, IOException {
179                    return this.doRequest( new RequestConfigDelegate( args, new HttpHead(), null ) );
180            }
181            
182            /** 
183             * <p>Perform a DELETE request.  This method does not accept a 
184             * <code>body</code> argument.</p>
185             * 
186             * @param args named parameters - see 
187             *      {@link HTTPBuilder.RequestConfigDelegate#setPropertiesFromMap(Map)}
188             * @return a {@link HttpResponseDecorator}, unless the default success 
189             *              handler is overridden.
190             * @throws ClientProtocolException
191             * @throws IOException
192             * @throws URISyntaxException
193             */
194            public Object delete( Map<String,?> args ) throws URISyntaxException, 
195                            ClientProtocolException, IOException {
196                    return this.doRequest( new RequestConfigDelegate( args, new HttpDelete(), null ) );
197            }
198            
199            /** 
200             * <p>Perform an OPTIONS request.</p>
201             * 
202             * @param args named parameters - see 
203             *      {@link HTTPBuilder.RequestConfigDelegate#setPropertiesFromMap(Map)}
204             * @return a {@link HttpResponseDecorator}, unless the default success 
205             *              handler is overridden.
206             * @throws ClientProtocolException
207             * @throws IOException
208             * @throws URISyntaxException
209             */
210            public Object options( Map<String,?> args ) throws ClientProtocolException, 
211                            IOException, URISyntaxException {
212                    return this.doRequest( new RequestConfigDelegate( args, new HttpOptions(), null ) );
213            }
214            
215            /**
216             * Returns an {@link HttpResponseDecorator}, which provides simplified 
217             * access to headers, response code, and parsed response body, as well as
218             * the underlying {@link HttpResponse} instance.
219             */
220            @Override
221            protected HttpResponseDecorator defaultSuccessHandler( HttpResponseDecorator resp, Object data )
222                            throws ResponseParseException {
223                    resp.setData( super.defaultSuccessHandler( resp, data ) );
224                    return resp;
225            }
226            
227            /**
228             * Throws an exception for non-successful HTTP response codes.  The 
229             * exception instance will have a reference to the response object, in 
230             * order to inspect status code and headers within the <code>catch</code> 
231             * block.
232             * @param resp response object
233             * @param data parsed response data
234             * @throws HttpResponseException exception which can access the response 
235             *   object.
236             */
237            protected void defaultFailureHandler( HttpResponseDecorator resp, Object data ) 
238                            throws HttpResponseException {
239                    resp.setData( super.defaultSuccessHandler( resp, data ) ); 
240                    throw new HttpResponseException( resp );
241            }
242    }