/* Copyright (c) 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.wave.api; import com.google.gson.Gson; import com.google.wave.api.JsonRpcConstant.ParamsProperty; import com.google.wave.api.impl.GsonFactory; import com.google.wave.api.impl.WaveletData; import net.oauth.OAuth; import net.oauth.OAuthAccessor; import net.oauth.OAuthConsumer; import net.oauth.OAuthException; import net.oauth.OAuthMessage; import net.oauth.OAuthValidator; import net.oauth.SimpleOAuthValidator; import net.oauth.client.OAuthClient; import net.oauth.http.HttpClient; import net.oauth.http.HttpMessage; import net.oauth.http.HttpResponseMessage; import net.oauth.signature.OAuthSignatureMethod; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.digest.DigestUtils; import org.waveprotocol.wave.model.id.InvalidIdException; import org.waveprotocol.wave.model.id.WaveId; import org.waveprotocol.wave.model.id.WaveletId; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URISyntaxException; import java.net.URL; import java.net.URLEncoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.AbstractMap.SimpleEntry; import java.util.logging.Logger; /** * Utility class for using OAuth to talk to Wave service. */ public class WaveService { /** * Helper class to make outgoing OAuth HTTP requests. */ static class HttpFetcher implements HttpClient { /** The {@code urlfetch} fetch timeout in ms. */ private static final int URLFETCH_TIMEOUT_IN_MS = 10 * 1000; private static final String HTTP_POST_METHOD = "POST"; private static final String HTTP_PUT_METHOD = "PUT"; @Override public HttpResponseMessage execute(HttpMessage request, Map<String, Object> stringObjectMap) throws IOException { String body = readInputStream(request.getBody()); OutputStreamWriter out = null; HttpURLConnection conn = null; // Open the connection. conn = (HttpURLConnection) request.url.openConnection(); conn.setReadTimeout(URLFETCH_TIMEOUT_IN_MS); conn.setRequestMethod(request.method); // Add the headers if (request.headers != null) { for (java.util.Map.Entry<String, String> header : request.headers) { conn.setRequestProperty(header.getKey(), header.getValue()); } } boolean doOutput = body != null && (HTTP_POST_METHOD.equalsIgnoreCase(request.method) || HTTP_PUT_METHOD.equalsIgnoreCase(request.method)); if (doOutput) { conn.setDoOutput(true); } conn.connect(); if (doOutput) { // Send the request body. out = new OutputStreamWriter(conn.getOutputStream(), UTF_8); try { out.write(body); out.flush(); } finally { out.close(); } } // Return the response stream. return new HttpResponse( request.method, request.url, conn.getResponseCode(), conn.getInputStream()); } /** * Reads the given {@link java.io.InputStream} into a {@link String} * * @param inputStream the {@link java.io.InputStream} to be read. * @return a string content of the {@link java.io.InputStream}. * @throws IOException if there is a problem reading the stream. */ static String readInputStream(InputStream inputStream) throws IOException { if (inputStream == null) { return null; } BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); StringBuilder result = new StringBuilder(); String s; while ((s = reader.readLine()) != null) { result.append(s); } return result.toString(); } } /** * A simple implementation of {@link HttpResponseMessage} that gets the * response from {@link HttpURLConnection#getInputStream()}. */ static class HttpResponse extends HttpResponseMessage { /** The HTTP response code. */ private final int statusCode; /** The response stream. */ private final InputStream responseStream; /** * Constructor. * * @param method the HTTP method, for example, GET or POST. * @param url the URL where the response comes from. * @param statusCode the HTTP response code. * @param responseStream the response stream. */ public HttpResponse(String method, URL url, int statusCode, InputStream responseStream) { super(method, url); this.statusCode = statusCode; this.responseStream = responseStream; } @Override public int getStatusCode() { return statusCode; } @Override public InputStream openBody() { return responseStream; } } /** * Helper class that contains various OAuth credentials. */ static class ConsumerData { /** Consumer key used to sign the operations in the active mode. */ private final String consumerKey; /** Consumer secret used to sign the operations in the active mode. */ private final String consumerSecret; /** The URL that handles the JSON-RPC request in the active mode. */ private final String rpcServerUrl; /** Whether this session is user authenticated */ private final boolean userAuthenticated; /** The OAuth Accessor contains authentication data used to make requests */ private final OAuthAccessor accessor; /** * Constructor. * * @param consumerKey the consumer key. * @param consumerSecret the consumer secret * @param rpcServerUrl the URL of the JSON-RPC request handler */ public ConsumerData(String consumerKey, String consumerSecret, String rpcServerUrl) { String consumerKeyPrefix = ""; // NOTE(ljvderijk): Present for backwards capability. if (RPC_URL.equals(rpcServerUrl) || SANDBOX_RPC_URL.equals(rpcServerUrl)) { consumerKeyPrefix = "google.com:"; } this.consumerKey = consumerKeyPrefix + consumerKey; this.consumerSecret = consumerSecret; this.rpcServerUrl = rpcServerUrl; userAuthenticated = false; OAuthConsumer consumer = new OAuthConsumer(null, consumerKey, consumerSecret, null); consumer.setProperty(OAuth.OAUTH_SIGNATURE_METHOD, OAuth.HMAC_SHA1); accessor = new OAuthAccessor(consumer); } public ConsumerData(OAuthAccessor accessor, String rpcServerUrl) { this.consumerKey = accessor.consumer.consumerKey; this.consumerSecret = accessor.consumer.consumerSecret; this.accessor = accessor; this.rpcServerUrl = rpcServerUrl; userAuthenticated = true; } /** * @return the consumer key used to sign the operations in the active mode. */ public String getConsumerKey() { return consumerKey; } /** * @return the consumer secret used to sign the operations in the active mode. */ public String getConsumerSecret() { return consumerSecret; } /** * @return the URL of the JSON-RPC request handler. */ public String getRpcServerUrl() { return rpcServerUrl; } public boolean isUserAuthenticated() { return userAuthenticated; } public OAuthAccessor getAccessor() { return accessor; } } /** The wire protocol version. */ public static final ProtocolVersion PROTOCOL_VERSION = ProtocolVersion.DEFAULT; private static final String JSON_MIME_TYPE = "application/json; charset=utf-8"; private static final String OAUTH_BODY_HASH = "oauth_body_hash"; private static final String POST = "POST"; private static final String SHA_1 = "SHA-1"; private static final String UTF_8 = "UTF-8"; /** Wave RPC URLs. */ public static final String RPC_URL = "https://www-opensocial.googleusercontent.com/api/rpc"; public static final String SANDBOX_RPC_URL = "https://www-opensocial-sandbox.googleusercontent.com/api/rpc"; private static final Logger LOG = Logger.getLogger(WaveService.class.getName()); /** Namespace to prefix all active api operation calls. */ private static final String OPERATION_NAMESPACE = "wave"; /** Serializer to serialize events and operations in active mode. */ private static final Gson SERIALIZER = new GsonFactory().create(OPERATION_NAMESPACE); /** OAuth request validator. */ private static final OAuthValidator VALIDATOR = new SimpleOAuthValidator(); /** A map of RPC server URL to its consumer data object. */ private final Map<String, ConsumerData> consumerDataMap = new HashMap<String, ConsumerData>(); /** A version number. */ private final String version; /** A utility to make HTTP requests. */ private final HttpFetcher httpFetcher; /** * Constructor. */ public WaveService() { this(new HttpFetcher(), null); } /** * Constructor. * * @param version the version number. */ public WaveService(String version) { this(new HttpFetcher(), version); } /** * Constructor. * * @param httpFetcher the fetcher to make HTTP calls. * @param version the version number. */ public WaveService(HttpFetcher httpFetcher, String version) { this.httpFetcher = httpFetcher; this.version = version; } /** * Sets the OAuth related properties, including the consumer key and secret * that are used to sign the outgoing operations. * * <p> * This version of the method is for 2-legged OAuth, where the robot is not * acting on behalf of a user. * * <p> * For the rpcServerUrl you can use: * <ul> * <li>https://www-opensocial.googleusercontent.com/api/rpc - for wave * preview. * <li> * https://www-opensocial-sandbox.googleusercontent.com/api/rpc - for wave * sandbox. * </ul> * * @param consumerKey the consumer key. * @param consumerSecret the consumer secret. * @param rpcServerUrl the URL of the server that serves the JSON-RPC request. */ public void setupOAuth(String consumerKey, String consumerSecret, String rpcServerUrl) { if (consumerKey == null || consumerSecret == null || rpcServerUrl == null) { throw new IllegalArgumentException( "Consumer Key, Consumer Secret and RPCServerURL " + "have to be non-null"); } consumerDataMap.put(rpcServerUrl, new ConsumerData(consumerKey, consumerSecret, rpcServerUrl)); } /** * Sets the OAuth related properties that are used to sign the outgoing * operations for 3-legged OAuth. * * <p> * Performing the OAuth dance is not part of this interface - once you've done * the dance, pass the constructed accessor and rpc endpoint into this method. * * <p> * Ensure that the endpoint URL you pass in matches exactly the URL used to * request an access token (including https vs http). * * For the rpcServerUrl you can use: * <ul> * <li>https://www-opensocial.googleusercontent.com/api/rpc - for wave * preview. * <li> * https://www-opensocial-sandbox.googleusercontent.com/api/rpc - for wave * sandbox. * </ul> * * @param accessor the {@code OAuthAccessor} with access token and secret * @param rpcServerUrl the endpoint URL of the server that serves the JSON-RPC * request. */ public void setupOAuth(OAuthAccessor accessor, String rpcServerUrl) { if (accessor == null || rpcServerUrl == null) { throw new IllegalArgumentException("Accessor and RPCServerURL have to be non-null"); } consumerDataMap.put(rpcServerUrl, new ConsumerData(accessor, rpcServerUrl)); } /** * Validates the incoming HTTP request. * * @param requestUrl the URL of the request. * @param jsonBody the request body to be validated. * @param rpcServerUrl the RPC server URL. * * @throws OAuthException if it can't validate the request. */ public void validateOAuthRequest( String requestUrl, Map<String, String[]> requestParams, String jsonBody, String rpcServerUrl) throws OAuthException { ConsumerData consumerData = consumerDataMap.get(rpcServerUrl); if (consumerData == null) { throw new IllegalArgumentException( "There is no consumer key and secret associated " + "with the given RPC URL " + rpcServerUrl); } List<OAuth.Parameter> params = new ArrayList<OAuth.Parameter>(); for (Map.Entry<String, String[]> entry : requestParams.entrySet()) { for (String value : entry.getValue()) { params.add(new OAuth.Parameter(entry.getKey(), value)); } } OAuthMessage message = new OAuthMessage(POST, requestUrl, params); // Compute and check the hash of the body. try { MessageDigest md = MessageDigest.getInstance(SHA_1); byte[] hash = md.digest(jsonBody.getBytes(UTF_8)); String encodedHash = new String(Base64.encodeBase64(hash, false), UTF_8); if (!encodedHash.equals(message.getParameter(OAUTH_BODY_HASH))) { throw new IllegalArgumentException( "Body hash does not match. Expected: " + encodedHash + ", provided: " + message.getParameter(OAUTH_BODY_HASH)); } OAuthAccessor accessor = consumerData.getAccessor(); LOG.info("Signature base string: " + OAuthSignatureMethod.getBaseString(message)); VALIDATOR.validateMessage(message, accessor); } catch (NoSuchAlgorithmException e) { throw new OAuthException("Error validating OAuth request", e); } catch (URISyntaxException e) { throw new OAuthException("Error validating OAuth request", e); } catch (OAuthException e) { throw new OAuthException("Error validating OAuth request", e); } catch (IOException e) { throw new OAuthException("Error validating OAuth request", e); } } /** * Submits the pending operations associated with this {@link Wavelet}. * * @param wavelet the bundle that contains the operations to be submitted. * @param rpcServerUrl the active gateway to send the operations to. * @return a list of {@link JsonRpcResponse} that represents the responses * from the server for all operations that were submitted. * * @throws IllegalStateException if this method is called prior to setting the * proper consumer key, secret, and handler URL. * @throws IOException if there is a problem submitting the operations. */ public List<JsonRpcResponse> submit(Wavelet wavelet, String rpcServerUrl) throws IOException { List<JsonRpcResponse> responses = makeRpc(wavelet.getOperationQueue(), rpcServerUrl); wavelet.getOperationQueue().clear(); return responses; } /** * Returns an empty/blind stub of a wavelet with the given wave id and wavelet * id. * * <p> * Call this method if you would like to apply wavelet-only operations * without fetching the wave first. * * The returned wavelet has its own {@link OperationQueue}. It is the * responsibility of the caller to make sure this wavelet gets submitted to * the server, either by calling {@link AbstractRobot#submit(Wavelet, String)} * or by calling {@link Wavelet#submitWith(Wavelet)} on the new wavelet, to * join its queue with another wavelet, for example, the event wavelet. * * @param waveId the id of the wave. * @param waveletId the id of the wavelet. * @return a stub of a wavelet. */ public Wavelet blindWavelet(WaveId waveId, WaveletId waveletId) { return blindWavelet(waveId, waveletId, null); } /** * @see #blindWavelet(WaveId, WaveletId) * * @param proxyForId the proxying information that should be set on the * operation queue. Please note that this parameter should be properly * encoded to ensure that the resulting participant id is valid (see * {@link Util#checkIsValidProxyForId(String)} for more details). */ public Wavelet blindWavelet(WaveId waveId, WaveletId waveletId, String proxyForId) { return blindWavelet(waveId, waveletId, proxyForId, new HashMap<String, Blip>()); } /** * @see #blindWavelet(WaveId, WaveletId, String) * * @param blips a collection of blips that belong to this wavelet. */ public Wavelet blindWavelet( WaveId waveId, WaveletId waveletId, String proxyForId, Map<String, Blip> blips) { return blindWavelet(waveId, waveletId, proxyForId, blips, new HashMap<String, BlipThread>()); } /** * @see #blindWavelet(WaveId, WaveletId, String, Map) * * @param threads a collection of threads that belong to this wavelet. */ public Wavelet blindWavelet(WaveId waveId, WaveletId waveletId, String proxyForId, Map<String, Blip> blips, Map<String, BlipThread> threads) { Util.checkIsValidProxyForId(proxyForId); Map<String, String> roles = new HashMap<String, String>(); return new Wavelet(waveId, waveletId, null, new BlipThread("", -1, new ArrayList<String>(), blips), Collections.<String>emptySet(), roles, blips, threads, new OperationQueue(proxyForId)); } /** * Creates a new wave with a list of participants on it. * * The root wavelet of the new wave is returned with its own * {@link OperationQueue}. It is the responsibility of the caller to make sure * this wavelet gets submitted to the server, either by calling * {@link AbstractRobot#submit(Wavelet, String)} or by calling * {@link Wavelet#submitWith(Wavelet)} on the new wavelet. * * @param domain the domain to create the wavelet on. In general, this should * correspond to the domain of the incoming event wavelet, except when * the robot is calling this method outside of an event or when the * server is handling multiple domains. * @param participants the initial participants on the wave. The robot, as the * creator of the wave, will be added by default. The order of the * participants will be preserved. */ public Wavelet newWave(String domain, Set<String> participants) { return newWave(domain, participants, null); } /** * @see #newWave(String, Set) * * @param proxyForId the proxy id that should be used to create the new wave. * If specified, the creator of the wave would be * robotid+<proxyForId>@appspot.com. Please note that this parameter * should be properly encoded to ensure that the resulting participant * id is valid (see {@link Util#checkIsValidProxyForId(String)} for * more details). */ public Wavelet newWave(String domain, Set<String> participants, String proxyForId) { return newWave(domain, participants, "", proxyForId); } /** * @see #newWave(String, Set, String) * * @param msg the message that will be passed back to the robot when * WAVELET_CREATED event is fired as a result of this operation. */ public Wavelet newWave(String domain, Set<String> participants, String msg, String proxyForId) { Util.checkIsValidProxyForId(proxyForId); return new OperationQueue(proxyForId).createWavelet(domain, participants, msg); } /** * @see #newWave(String, Set, String, String) * * @param rpcServerUrl if specified, this operation will be submitted * immediately to this active gateway, that will return immediately the * actual wave id, the id of the root wavelet, and id of the root blip. * * @throws IOException if there is a problem submitting the operation to the * server, when {@code submit} is {@code true}. * @throws InvalidIdException */ public Wavelet newWave( String domain, Set<String> participants, String msg, String proxyForId, String rpcServerUrl) throws IOException, InvalidIdException { Util.checkIsValidProxyForId(proxyForId); OperationQueue opQueue = new OperationQueue(proxyForId); Wavelet newWavelet = opQueue.createWavelet(domain, participants, msg); if (rpcServerUrl != null && !rpcServerUrl.isEmpty()) { // Get the response for the robot.fetchWavelet() operation, which is the // second operation, since makeRpc prepends the robot.notify() operation. JsonRpcResponse response = this.submit(newWavelet, rpcServerUrl).get(1); if (response.isError()) { throw new IOException(response.getErrorMessage()); } WaveId waveId = ApiIdSerializer.instance().deserialiseWaveId( (String) response.getData().get(ParamsProperty.WAVE_ID)); WaveletId waveletId = ApiIdSerializer.instance().deserialiseWaveletId( (String) response.getData().get(ParamsProperty.WAVELET_ID)); String rootBlipId = (String) response.getData().get(ParamsProperty.BLIP_ID); Map<String, Blip> blips = new HashMap<String, Blip>(); Map<String, BlipThread> threads = new HashMap<String, BlipThread>(); Map<String, String> roles = new HashMap<String, String>(); List<String> blipIds = new ArrayList<String>(); blipIds.add(rootBlipId); BlipThread rootThread = new BlipThread("", -1, blipIds, blips); newWavelet = new Wavelet(waveId, waveletId, rootBlipId, rootThread, participants, roles, blips, threads, opQueue); blips.put(rootBlipId, new Blip(rootBlipId, "", null, "", newWavelet)); } return newWavelet; } /** * Requests SearchResult for a query. * * @param query the query to execute. * @param index the index from which to return results. * @param numresults the number of results to return. * @param rpcServerUrl the active gateway. * * @throws IOException if remote server returns error. */ public SearchResult search(String query, Integer index, Integer numResults, String rpcServerUrl) throws IOException { OperationQueue opQueue = new OperationQueue(); opQueue.search(query, index, numResults); JsonRpcResponse response = makeRpc(opQueue, rpcServerUrl).get(1); if (response.isError()) { throw new IOException(response.getErrorMessage()); } opQueue.clear(); SearchResult searchResult = (SearchResult) response.getData().get(ParamsProperty.SEARCH_RESULTS); return searchResult; } /** * Fetches a wavelet using the active API. * * The returned wavelet contains a snapshot of the state of the wavelet at * that point. It can be used to modify the wavelet, but the wavelet might * change in between, so treat carefully. * * Also, the returned wavelet has its own {@link OperationQueue}. It is the * responsibility of the caller to make sure this wavelet gets submitted to * the server, either by calling {@link AbstractRobot#submit(Wavelet, String)} * or by calling {@link Wavelet#submitWith(Wavelet)} on the new wavelet. * * @param waveId the id of the wave to fetch. * @param waveletId the id of the wavelet to fetch. * @param rpcServerUrl the active gateway that is used to fetch the wavelet. * * @throws IOException if there is a problem fetching the wavelet. */ public Wavelet fetchWavelet(WaveId waveId, WaveletId waveletId, String rpcServerUrl) throws IOException { return fetchWavelet(waveId, waveletId, null, rpcServerUrl); } /** * @see #fetchWavelet(WaveId, WaveletId, String) * * @param proxyForId the proxy id that should be used to fetch this wavelet. * Please note that this parameter should be properly encoded to ensure * that the resulting participant id is valid (see * {@link Util#checkIsValidProxyForId(String)} for more details). */ public Wavelet fetchWavelet( WaveId waveId, WaveletId waveletId, String proxyForId, String rpcServerUrl) throws IOException { Util.checkIsValidProxyForId(proxyForId); OperationQueue opQueue = new OperationQueue(proxyForId); opQueue.fetchWavelet(waveId, waveletId); // Get the response for the robot.fetchWavelet() operation, which is the // second operation, since makeRpc prepends the robot.notify() operation. JsonRpcResponse response = makeRpc(opQueue, rpcServerUrl).get(1); if (response.isError()) { throw new IOException(response.getErrorMessage()); } // Deserialize wavelet. opQueue.clear(); WaveletData waveletData = (WaveletData) response.getData().get(ParamsProperty.WAVELET_DATA); Map<String, Blip> blips = new HashMap<String, Blip>(); Map<String, BlipThread> threads = new HashMap<String, BlipThread>(); Wavelet wavelet = Wavelet.deserialize(opQueue, blips, threads, waveletData); // Deserialize threads. @SuppressWarnings("unchecked") Map<String, BlipThread> tempThreads = (Map<String, BlipThread>) response.getData().get(ParamsProperty.THREADS); for (Map.Entry<String, BlipThread> entry : tempThreads.entrySet()) { BlipThread thread = entry.getValue(); threads.put(entry.getKey(), new BlipThread(thread.getId(), thread.getLocation(), thread.getBlipIds(), blips)); } // Deserialize blips. @SuppressWarnings("unchecked") Map<String, BlipData> blipDatas = (Map<String, BlipData>) response.getData().get(ParamsProperty.BLIPS); for (Map.Entry<String, BlipData> entry : blipDatas.entrySet()) { blips.put(entry.getKey(), Blip.deserialize(opQueue, wavelet, entry.getValue())); } return wavelet; } /** * @return the map of consumer key and secret. */ protected Map<String, ConsumerData> getConsumerDataMap() { return consumerDataMap; } /** * @return {@code true} if this service object contains a consumer key and * secret for the given RPC server URL. */ protected boolean hasConsumerData(String rpcServerUrl) { return consumerDataMap.containsKey(rpcServerUrl); } /** * Submits the given operations. * * @param opQueue the operation queue to be submitted. * @param rpcServerUrl the active gateway to send the operations to. * @return a list of {@link JsonRpcResponse} that represents the responses * from the server for all operations that were submitted. * * @throws IllegalStateException if this method is called prior to setting the * proper consumer key, secret, and handler URL. * @throws IOException if there is a problem submitting the operations. */ private List<JsonRpcResponse> makeRpc(OperationQueue opQueue, String rpcServerUrl) throws IOException { if (rpcServerUrl == null) { throw new IllegalStateException("RPC Server URL is not set up."); } ConsumerData consumerDataObj = consumerDataMap.get(rpcServerUrl); if (consumerDataObj == null) { throw new IllegalStateException("Consumer key, consumer secret, and JSON-RPC server URL " + "have to be set first, by calling AbstractRobot.setupOAuth(), before invoking " + "AbstractRobot.submit()."); } opQueue.notifyRobotInformation(PROTOCOL_VERSION, version); String json = SERIALIZER.toJson(opQueue.getPendingOperations(), GsonFactory.OPERATION_REQUEST_LIST_TYPE); try { InputStream bodyStream; InputStream responseStream; try { bodyStream = new ByteArrayInputStream(json.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { throw new IllegalStateException(e); } if (!consumerDataObj.isUserAuthenticated()) { String url = createOAuthUrlString( json, consumerDataObj.getRpcServerUrl(), consumerDataObj.getAccessor()); LOG.info("JSON request to be sent: " + json); HttpMessage request = new HttpMessage("POST", new URL(url), bodyStream); request.headers.add( new SimpleEntry<String, String>(HttpMessage.CONTENT_TYPE, JSON_MIME_TYPE)); request.headers.add(new SimpleEntry<String, String>("oauth_version", "1.0")); responseStream = httpFetcher.execute(request, Collections.<String, Object>emptyMap()).getBody(); } else { OAuthAccessor accessor = consumerDataObj.getAccessor(); OAuthMessage message = accessor.newRequestMessage("POST", rpcServerUrl, null, bodyStream); message.getHeaders().add( new SimpleEntry<String, String>(HttpMessage.CONTENT_TYPE, "application/json")); message.getHeaders().add(new SimpleEntry<String, String>("oauth_version", "1.0")); OAuthClient client = new OAuthClient(httpFetcher); responseStream = client.invoke(message, net.oauth.ParameterStyle.BODY).getBodyAsStream(); } String responseString = HttpFetcher.readInputStream(responseStream); LOG.info("Response returned: " + responseString); List<JsonRpcResponse> responses = null; if (responseString.startsWith("[")) { responses = SERIALIZER.fromJson(responseString, GsonFactory.JSON_RPC_RESPONSE_LIST_TYPE); } else { responses = new ArrayList<JsonRpcResponse>(1); responses.add(SERIALIZER.fromJson(responseString, JsonRpcResponse.class)); } return responses; } catch (OAuthException e) { LOG.warning("OAuthException when constructing the OAuth parameters: " + e); throw new IOException(e); } catch (URISyntaxException e) { LOG.warning("URISyntaxException when constructing the OAuth parameters: " + e); throw new IOException(e); } } /** * Creates a URL that contains the necessary OAuth query parameters for the * given JSON string. * * The required OAuth parameters are: * <ul> * <li>oauth_body_hash</li> * <li>oauth_consumer_key</li> * <li>oauth_signature_method</li> * <li>oauth_timestamp</li> * <li>oauth_nonce</li> * <li>oauth_version</li> * <li>oauth_signature</li> * </ul> * * @param jsonBody the JSON string to construct the URL from. * @param rpcServerUrl the URL of the handler that services the JSON-RPC * request. * @param accessor the OAuth accessor used to create the signed string. * @return a URL for the given JSON string, and the required OAuth parameters. */ public static String createOAuthUrlString( String jsonBody, String rpcServerUrl, OAuthAccessor accessor) throws IOException, URISyntaxException, OAuthException { OAuthMessage message = new OAuthMessage(POST, rpcServerUrl, Collections.<SimpleEntry<String, String>>emptyList()); // Compute the hash of the body. byte[] rawBody = jsonBody.getBytes(UTF_8); byte[] hash = DigestUtils.sha(rawBody); byte[] encodedHash = Base64.encodeBase64(hash); message.addParameter(OAUTH_BODY_HASH, new String(encodedHash, UTF_8)); // Add other parameters. message.addRequiredParameters(accessor); LOG.info("Signature base string: " + OAuthSignatureMethod.getBaseString(message)); // Construct the resulting URL. StringBuilder sb = new StringBuilder(rpcServerUrl); char connector = '?'; for (Map.Entry<String, String> p : message.getParameters()) { if (!p.getKey().equals(jsonBody)) { sb.append(connector); sb.append(URLEncoder.encode(p.getKey(), UTF_8)); sb.append('='); sb.append(URLEncoder.encode(p.getValue(), UTF_8)); connector = '&'; } } return sb.toString(); } }