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