1b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien/*
2b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * Copyright 2007, 2008 Netflix, Inc.
3b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien *
4b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * Licensed under the Apache License, Version 2.0 (the "License");
5b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * you may not use this file except in compliance with the License.
6b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * You may obtain a copy of the License at
7b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien *
8b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien *     http://www.apache.org/licenses/LICENSE-2.0
9b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien *
10b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * Unless required by applicable law or agreed to in writing, software
11b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * distributed under the License is distributed on an "AS IS" BASIS,
12b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * See the License for the specific language governing permissions and
14b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * limitations under the License.
15b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien */
16b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien
17b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienpackage net.oauth.client;
18b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien
19b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienimport java.io.ByteArrayInputStream;
20b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienimport java.io.IOException;
21b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienimport java.io.InputStream;
22b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienimport java.net.URISyntaxException;
23b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienimport java.net.URL;
24b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienimport java.util.ArrayList;
25b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienimport java.util.Collection;
26b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienimport java.util.Iterator;
27b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienimport java.util.List;
28b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienimport java.util.Map;
29b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienimport net.oauth.OAuth;
30b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienimport net.oauth.OAuthAccessor;
31b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienimport net.oauth.OAuthConsumer;
32b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienimport net.oauth.OAuthException;
33b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienimport net.oauth.OAuthMessage;
34b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienimport net.oauth.OAuthProblemException;
35b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienimport net.oauth.http.HttpClient;
36b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienimport net.oauth.http.HttpMessage;
37b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienimport net.oauth.http.HttpMessageDecoder;
38b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienimport net.oauth.http.HttpResponseMessage;
39b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien
40b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien/**
41b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * Methods for an OAuth consumer to request tokens from a service provider.
42b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * <p>
43b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * This class can also be used to request access to protected resources, in some
44b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * cases. But not in all cases. For example, this class can't handle arbitrary
45b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * HTTP headers.
46b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * <p>
47b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * Methods of this class return a response as an OAuthMessage, from which you
48b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * can get a body or parameters but not both. Calling a getParameter method will
49b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * read and close the body (like readBodyAsString), so you can't read it later.
50b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * If you read or close the body first, then getParameter can't read it. The
51b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * response headers should tell you whether the response contains encoded
52b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * parameters, that is whether you should call getParameter or not.
53b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * <p>
54b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * Methods of this class don't follow redirects. When they receive a redirect
55b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * response, they throw an OAuthProblemException, with properties
56b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * HttpResponseMessage.STATUS_CODE = the redirect code
57b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * HttpResponseMessage.LOCATION = the redirect URL. Such a redirect can't be
58b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * handled at the HTTP level, if the second request must carry another OAuth
59b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * signature (with different parameters). For example, Google's Service Provider
60b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * routinely redirects requests for access to protected resources, and requires
61b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * the redirected request to be signed.
62b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien *
63b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * @author John Kristian
64b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien * @hide
65b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien */
66b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembienpublic class OAuthClient {
67b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien
68b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    public OAuthClient(HttpClient http)
69b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    {
70b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        this.http = http;
71b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    }
72b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien
73b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    private HttpClient http;
74b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien
75b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    public void setHttpClient(HttpClient http) {
76b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        this.http = http;
77b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    }
78b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien
79b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    public HttpClient getHttpClient() {
80b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        return http;
81b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    }
82b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien
83b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    /**
84b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * Get a fresh request token from the service provider.
85b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *
86b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @param accessor
87b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            should contain a consumer that contains a non-null consumerKey
88b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            and consumerSecret. Also,
89b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            accessor.consumer.serviceProvider.requestTokenURL should be
90b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            the URL (determined by the service provider) for getting a
91b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            request token.
92b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @throws OAuthProblemException
93b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *             the HTTP response status code was not 200 (OK)
94b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     */
95b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    public void getRequestToken(OAuthAccessor accessor) throws IOException,
96b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            OAuthException, URISyntaxException {
97b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        getRequestToken(accessor, null);
98b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    }
99b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien
100b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    /**
101b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * Get a fresh request token from the service provider.
102b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *
103b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @param accessor
104b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            should contain a consumer that contains a non-null consumerKey
105b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            and consumerSecret. Also,
106b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            accessor.consumer.serviceProvider.requestTokenURL should be
107b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            the URL (determined by the service provider) for getting a
108b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            request token.
109b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @param httpMethod
110b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            typically OAuthMessage.POST or OAuthMessage.GET, or null to
111b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            use the default method.
112b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @throws OAuthProblemException
113b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *             the HTTP response status code was not 200 (OK)
114b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     */
115b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    public void getRequestToken(OAuthAccessor accessor, String httpMethod)
116b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            throws IOException, OAuthException, URISyntaxException {
117b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        getRequestToken(accessor, httpMethod, null);
118b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    }
119b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien
120b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    /** Get a fresh request token from the service provider.
121b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *
122b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @param accessor
123b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            should contain a consumer that contains a non-null consumerKey
124b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            and consumerSecret. Also,
125b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            accessor.consumer.serviceProvider.requestTokenURL should be
126b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            the URL (determined by the service provider) for getting a
127b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            request token.
128b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @param httpMethod
129b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            typically OAuthMessage.POST or OAuthMessage.GET, or null to
130b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            use the default method.
131b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @param parameters
132b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            additional parameters for this request, or null to indicate
133b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            that there are no additional parameters.
134b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @throws OAuthProblemException
135b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *             the HTTP response status code was not 200 (OK)
136b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     */
137b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    public void getRequestToken(OAuthAccessor accessor, String httpMethod,
138b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            Collection<? extends Map.Entry> parameters) throws IOException,
139b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            OAuthException, URISyntaxException {
140b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        accessor.accessToken = null;
141b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        accessor.tokenSecret = null;
142b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        {
143b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            // This code supports the 'Variable Accessor Secret' extension
144b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            // described in http://oauth.pbwiki.com/AccessorSecret
145b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            Object accessorSecret = accessor
146b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                    .getProperty(OAuthConsumer.ACCESSOR_SECRET);
147b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            if (accessorSecret != null) {
148b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                List<Map.Entry> p = (parameters == null) ? new ArrayList<Map.Entry>(
149b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                        1)
150b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                        : new ArrayList<Map.Entry>(parameters);
151b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                p.add(new OAuth.Parameter("oauth_accessor_secret",
152b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                        accessorSecret.toString()));
153b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                parameters = p;
154b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                // But don't modify the caller's parameters.
155b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            }
156b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        }
157b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        OAuthMessage response = invoke(accessor, httpMethod,
158b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                accessor.consumer.serviceProvider.requestTokenURL, parameters);
159b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        accessor.requestToken = response.getParameter(OAuth.OAUTH_TOKEN);
160b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        accessor.tokenSecret = response.getParameter(OAuth.OAUTH_TOKEN_SECRET);
161b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        response.requireParameters(OAuth.OAUTH_TOKEN, OAuth.OAUTH_TOKEN_SECRET);
162b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    }
163b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien
164b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    /**
165b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * Get an access token from the service provider, in exchange for an
166b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * authorized request token.
167b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *
168b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @param accessor
169b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            should contain a non-null requestToken and tokenSecret, and a
170b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            consumer that contains a consumerKey and consumerSecret. Also,
171b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            accessor.consumer.serviceProvider.accessTokenURL should be the
172b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            URL (determined by the service provider) for getting an access
173b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            token.
174b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @param httpMethod
175b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            typically OAuthMessage.POST or OAuthMessage.GET, or null to
176b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            use the default method.
177b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @param parameters
178b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            additional parameters for this request, or null to indicate
179b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            that there are no additional parameters.
180b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @throws OAuthProblemException
181b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *             the HTTP response status code was not 200 (OK)
182b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     */
183b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    public OAuthMessage getAccessToken(OAuthAccessor accessor, String httpMethod,
184b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            Collection<? extends Map.Entry> parameters) throws IOException, OAuthException, URISyntaxException {
185b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        if (accessor.requestToken != null) {
186b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            if (parameters == null) {
187b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                parameters = OAuth.newList(OAuth.OAUTH_TOKEN, accessor.requestToken);
188b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            } else if (!OAuth.newMap(parameters).containsKey(OAuth.OAUTH_TOKEN)) {
189b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                List<Map.Entry> p = new ArrayList<Map.Entry>(parameters);
190b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                p.add(new OAuth.Parameter(OAuth.OAUTH_TOKEN, accessor.requestToken));
191b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                parameters = p;
192b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            }
193b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        }
194b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        OAuthMessage response = invoke(accessor, httpMethod,
195b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                accessor.consumer.serviceProvider.accessTokenURL, parameters);
196b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        response.requireParameters(OAuth.OAUTH_TOKEN, OAuth.OAUTH_TOKEN_SECRET);
197b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        accessor.accessToken = response.getParameter(OAuth.OAUTH_TOKEN);
198b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        accessor.tokenSecret = response.getParameter(OAuth.OAUTH_TOKEN_SECRET);
199b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        return response;
200b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    }
201b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien
202b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    /**
203b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * Construct a request message, send it to the service provider and get the
204b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * response.
205b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *
206b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @param httpMethod
207b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *            the HTTP request method, or null to use the default method
208b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @return the response
209b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @throws URISyntaxException
210b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *             the given url isn't valid syntactically
211b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @throws OAuthProblemException
212b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *             the HTTP response status code was not 200 (OK)
213b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     */
214b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    public OAuthMessage invoke(OAuthAccessor accessor, String httpMethod,
215b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            String url, Collection<? extends Map.Entry> parameters)
216b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    throws IOException, OAuthException, URISyntaxException {
217b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        String ps = (String) accessor.consumer.getProperty(PARAMETER_STYLE);
218b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        ParameterStyle style = (ps == null) ? ParameterStyle.BODY : Enum
219b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                .valueOf(ParameterStyle.class, ps);
220b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        OAuthMessage request = accessor.newRequestMessage(httpMethod, url,
221b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                parameters);
222b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        return invoke(request, style);
223b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    }
224b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien
225b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    /**
226b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * The name of the OAuthConsumer property whose value is the ParameterStyle
227b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * to be used by invoke.
228b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     */
229b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    public static final String PARAMETER_STYLE = "parameterStyle";
230b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien
231b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    /**
232b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * The name of the OAuthConsumer property whose value is the Accept-Encoding
233b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * header in HTTP requests.
234b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @deprecated use {@link OAuthConsumer#ACCEPT_ENCODING} instead
235b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     */
236b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    @Deprecated
237b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    public static final String ACCEPT_ENCODING = OAuthConsumer.ACCEPT_ENCODING;
238b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien
239b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    /**
240b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * Construct a request message, send it to the service provider and get the
241b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * response.
242b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *
243b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @return the response
244b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @throws URISyntaxException
245b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *                 the given url isn't valid syntactically
246b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @throws OAuthProblemException
247b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *                 the HTTP response status code was not 200 (OK)
248b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     */
249b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    public OAuthMessage invoke(OAuthAccessor accessor, String url,
250b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            Collection<? extends Map.Entry> parameters) throws IOException,
251b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            OAuthException, URISyntaxException {
252b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        return invoke(accessor, null, url, parameters);
253b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    }
254b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien
255b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    /**
256b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * Send a request message to the service provider and get the response.
257b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *
258b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @return the response
259b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @throws IOException
260b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *                 failed to communicate with the service provider
261b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     * @throws OAuthProblemException
262b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     *             the HTTP response status code was not 200 (OK)
263b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien     */
264b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    public OAuthMessage invoke(OAuthMessage request, ParameterStyle style)
265b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            throws IOException, OAuthException {
266b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        final boolean isPost = POST.equalsIgnoreCase(request.method);
267b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        InputStream body = request.getBodyAsStream();
268b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        if (style == ParameterStyle.BODY && !(isPost && body == null)) {
269b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            style = ParameterStyle.QUERY_STRING;
270b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        }
271b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        String url = request.URL;
272b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        final List<Map.Entry<String, String>> headers =
273b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            new ArrayList<Map.Entry<String, String>>(request.getHeaders());
274b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        switch (style) {
275b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        case QUERY_STRING:
276b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            url = OAuth.addParameters(url, request.getParameters());
277b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            break;
278b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        case BODY: {
279b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            byte[] form = OAuth.formEncode(request.getParameters()).getBytes(
280b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                    request.getBodyEncoding());
281b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            headers.add(new OAuth.Parameter(HttpMessage.CONTENT_TYPE,
282b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                    OAuth.FORM_ENCODED));
283b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            headers.add(new OAuth.Parameter(CONTENT_LENGTH, form.length + ""));
284b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            body = new ByteArrayInputStream(form);
285b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            break;
286b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        }
287b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        case AUTHORIZATION_HEADER:
288b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            headers.add(new OAuth.Parameter("Authorization", request.getAuthorizationHeader(null)));
289b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            // Find the non-OAuth parameters:
290b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            List<Map.Entry<String, String>> others = request.getParameters();
291b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            if (others != null && !others.isEmpty()) {
292b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                others = new ArrayList<Map.Entry<String, String>>(others);
293b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                for (Iterator<Map.Entry<String, String>> p = others.iterator(); p
294b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                        .hasNext();) {
295b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                    if (p.next().getKey().startsWith("oauth_")) {
296b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                        p.remove();
297b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                    }
298b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                }
299b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                // Place the non-OAuth parameters elsewhere in the request:
300b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                if (isPost && body == null) {
301b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                    byte[] form = OAuth.formEncode(others).getBytes(
302b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                            request.getBodyEncoding());
303b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                    headers.add(new OAuth.Parameter(HttpMessage.CONTENT_TYPE,
304b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                            OAuth.FORM_ENCODED));
305b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                    headers.add(new OAuth.Parameter(CONTENT_LENGTH, form.length
306b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                            + ""));
307b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                    body = new ByteArrayInputStream(form);
308b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                } else {
309b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                    url = OAuth.addParameters(url, others);
310b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                }
311b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            }
312b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            break;
313b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        }
314b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        final HttpMessage httpRequest = new HttpMessage(request.method, new URL(url), body);
315b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        httpRequest.headers.addAll(headers);
316b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        HttpResponseMessage httpResponse = http.execute(httpRequest);
317b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        httpResponse = HttpMessageDecoder.decode(httpResponse);
318b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        OAuthMessage response = new OAuthResponseMessage(httpResponse);
319b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        if (httpResponse.getStatusCode() != HttpResponseMessage.STATUS_OK) {
320b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            OAuthProblemException problem = new OAuthProblemException();
321b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            try {
322b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                response.getParameters(); // decode the response body
323b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            } catch (IOException ignored) {
324b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            }
325b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            problem.getParameters().putAll(response.getDump());
326b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            try {
327b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                InputStream b = response.getBodyAsStream();
328b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                if (b != null) {
329b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                    b.close(); // release resources
330b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien                }
331b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            } catch (IOException ignored) {
332b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            }
333b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien            throw problem;
334b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        }
335b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        return response;
336b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    }
337b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien
338b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    /** Where to place parameters in an HTTP message. */
339b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    public enum ParameterStyle {
340b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien        AUTHORIZATION_HEADER, BODY, QUERY_STRING;
341b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    };
342b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien
343b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    protected static final String PUT = OAuthMessage.PUT;
344b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    protected static final String POST = OAuthMessage.POST;
345b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    protected static final String DELETE = OAuthMessage.DELETE;
346b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien    protected static final String CONTENT_LENGTH = HttpMessage.CONTENT_LENGTH;
347b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien
348b852fcf48a8909164d7f323dd02a35d2a8056a61Nico Sallembien}
349