Java tutorial
/* Copyright (c) 2009 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 org.opensocial; import net.oauth.http.HttpMessage; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.opensocial.auth.AuthScheme; import org.opensocial.http.HttpClient; import org.opensocial.http.HttpClientImpl; import org.opensocial.http.HttpResponseMessage; import org.opensocial.providers.Provider; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; /** * OpenSocial RESTful client supporting both the RPC and REST protocols defined * in the OpenSocial specification as well as two- and three-legged OAuth for * authentication. This class handles the transmission of requests to * OpenSocial containers such as orkut and MySpace. Typical usage: * <pre> * Client client = new Client(new OrkutProvider(), new OAuth2LeggedScheme(ORKUT_KEY, ORKUT_SECRET, ORKUT_ID)); Response response = client.send(PeopleService.getViewer()); * </pre> * The send method either returns a single {@link Response} or a {@link Map} of * Response objects mapped to ID strings. The data returned from the container * can be extracted from these objects. * * @author Jason Cooper */ public class Client { private Provider provider; private AuthScheme authScheme; private HttpClient httpClient; private static Logger logger = Logger.getLogger("org.opensocial.client"); /** * Creates and returns a new {@link Client} associated with the passed * {@link Provider} and {@link AuthScheme}. * * @param provider Provider to associate with new Client * @param authScheme AuthScheme to associate with new Client */ public Client(Provider provider, AuthScheme authScheme) { this(provider, authScheme, new HttpClientImpl()); } /** * Creates and returns a new {@link Client} associated with the passed * {@link Provider} and {@link AuthScheme}. * * @param provider Provider to associate with new Client * @param authScheme AuthScheme to associate with new Client * @param httpClient HttpClient to use with new Client */ public Client(Provider provider, AuthScheme authScheme, HttpClient httpClient) { this.provider = provider; this.authScheme = authScheme; this.httpClient = httpClient; } /** * Returns the associated {@link Provider}. */ public Provider getProvider() { return provider; } /** * Returns the associated {@link AuthScheme}. */ public AuthScheme getAuthScheme() { return authScheme; } /** * Submits the passed {@link Request} to the associated {@link Provider} and * returns the container's response data as a {@link Response} object. * * @param request Request object (typically returned from static methods in * service classes) encapsulating all request data including * endpoint, HTTP method, and any required parameters * @return Response object encapsulating the response data returned * by the container * * @throws RequestException if the passed request cannot be serialized, the * container returns an error code, or the response * cannot be parsed * @throws IOException if an I/O error prevents a connection from being * opened or otherwise causes request transmission * to fail */ public Response send(Request request) throws RequestException, IOException { final String KEY = "key"; Map<String, Request> requests = new HashMap<String, Request>(); requests.put(KEY, request); Map<String, Response> responses = send(requests); return responses.get(KEY); } /** * Submits the passed {@link Map} of {@link Request}s to the associated * {@link Provider} and returns the container's response data as a Map of * {@link Response} objects mapped to the same IDs as the passed requests. If * the associated provider supports the OpenSocial RPC protocol, only one * HTTP request is sent; otherwise, one HTTP request is executed per * container request. * * @param requests Map of Request objects (typically returned from static * methods in service classes) to ID strings; each object * encapsulates the data for a single container request * such as fetching the viewer or creating an activity. * @return Map of Response objects, each encapsulating the response * data returned by the container for a single request, to * the associated ID strings in the passed Map of Request * objects * * @throws RequestException if the passed request cannot be serialized, the * container returns an error code, or the response * cannot be parsed * @throws IOException if an I/O error prevents a connection from being * opened or otherwise causes request transmission * to fail */ public Map<String, Response> send(Map<String, Request> requests) throws RequestException, IOException { if (requests.size() == 0) { throw new RequestException("Request queue is empty"); } Map<String, Response> responses = new HashMap<String, Response>(); if (provider.getRpcEndpoint() != null) { responses = submitRpc(requests); } else if (provider.getRestEndpoint() != null) { for (Map.Entry<String, Request> entry : requests.entrySet()) { Request request = entry.getValue(); provider.preRequest(request); Response response = submitRestRequest(request); responses.put(entry.getKey(), response); provider.postRequest(request, response); } } else { throw new RequestException("Provider has no REST or RPC endpoint set"); } return responses; } private Map<String, Response> submitRpc(Map<String, Request> requests) throws RequestException, IOException { Map<String, String> requestHeaders = new HashMap<String, String>(); if (requests.size() == 1 && requests.values().iterator().next().getContentType() != null) { requestHeaders.put(HttpMessage.CONTENT_TYPE, requests.values().iterator().next().getContentType()); } else { requestHeaders.put(HttpMessage.CONTENT_TYPE, provider.getContentType()); } HttpMessage message = authScheme.getHttpMessage(provider, "POST", buildRpcUrl(requests), requestHeaders, buildRpcPayload(requests)); HttpResponseMessage responseMessage = httpClient.execute(message); logger.finest(buildLogRecord(requests, responseMessage)); Map<String, Response> responses = Response.parseRpcResponse(requests, responseMessage, provider.getVersion()); return responses; } private Response submitRestRequest(Request request) throws RequestException, IOException { Map<String, String> requestHeaders = new HashMap<String, String>(); if (request.getContentType() != null) { requestHeaders.put(HttpMessage.CONTENT_TYPE, request.getContentType()); } else { requestHeaders.put(HttpMessage.CONTENT_TYPE, provider.getContentType()); } HttpMessage message = authScheme.getHttpMessage(provider, request.getRestMethod(), buildRestUrl(request), requestHeaders, buildRestPayload(request)); HttpResponseMessage responseMessage = httpClient.execute(message); logger.finest(buildLogRecord(request, responseMessage)); Response response = Response.parseRestResponse(request, responseMessage, provider.getVersion()); return response; } String buildRpcUrl(Map<String, Request> requests) { StringBuilder builder = new StringBuilder(provider.getRpcEndpoint()); // Remove trailing forward slash if (builder.charAt(builder.length() - 1) == '/') { builder.deleteCharAt(builder.length() - 1); } if (requests.size() == 1) { appendQueryString(builder, requests.values().iterator().next().getRpcQueryStringParameters()); } return builder.toString(); } byte[] buildRpcPayload(Map<String, Request> requests) { if (requests.size() == 1 && requests.values().iterator().next().getCustomPayload() != null) { return requests.values().iterator().next().getCustomPayload(); } JSONArray requestArray = new JSONArray(); for (Map.Entry<String, Request> requestEntry : requests.entrySet()) { JSONObject request = new JSONObject(); request.put("id", requestEntry.getKey()); request.put("method", requestEntry.getValue().getRpcMethod()); JSONObject requestParams = new JSONObject(); for (Map.Entry<String, Object> parameter : requestEntry.getValue().getRpcPayloadParameters() .entrySet()) { requestParams.put(parameter.getKey(), parameter.getValue()); } request.put("params", requestParams); requestArray.add(request); } try { return requestArray.toJSONString().getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { return null; } } String buildRestUrl(Request request) { StringBuilder builder = new StringBuilder(provider.getRestEndpoint()); String[] components = request.getRestUrlTemplate().split("/"); for (String component : components) { if (component.startsWith("{") && component.endsWith("}")) { String tag = component.substring(1, component.length() - 1); if (request.getComponent(tag) != null) { builder.append(request.getComponent(tag)); builder.append("/"); } } else { builder.append(component); builder.append("/"); } } // Remove trailing forward slash builder.deleteCharAt(builder.length() - 1); // Append query string parameters Map<String, String> parameters = request.getRestQueryStringParameters(); appendQueryString(builder, parameters); return builder.toString(); } byte[] buildRestPayload(Request request) { if (request.getCustomPayload() != null) { return request.getCustomPayload(); } Map<String, Object> parameters = request.getRestPayloadParameters(); if (parameters == null || parameters.size() == 0) { return null; } JSONObject payload = new JSONObject(); for (Map.Entry<String, Object> parameter : parameters.entrySet()) { payload.put(parameter.getKey(), parameter.getValue()); } try { return payload.toJSONString().getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { return null; } } private void appendQueryString(StringBuilder builder, Map<String, String> parameters) { if (parameters != null && parameters.size() > 0) { boolean runOnce = false; for (Map.Entry<String, String> parameter : parameters.entrySet()) { if (!runOnce) { builder.append("?"); runOnce = true; } else { builder.append("&"); } try { builder.append(URLEncoder.encode(parameter.getKey(), "UTF-8")); builder.append("="); builder.append(URLEncoder.encode(parameter.getValue(), "UTF-8")); } catch (UnsupportedEncodingException e) { // Ignore } } } } private String buildLogRecord(Map<String, Request> requests, HttpResponseMessage message) { String payload = null; byte[] bytes = buildRpcPayload(requests); if (bytes != null) { try { payload = new String(bytes, "UTF-8"); } catch (UnsupportedEncodingException e) { // Ignore } } return buildLogRecord(payload, message); } private String buildLogRecord(Request request, HttpResponseMessage message) { String payload = null; if (request.getCustomPayload() == null) { byte[] bytes = buildRestPayload(request); if (bytes != null) { try { payload = new String(bytes, "UTF-8"); } catch (UnsupportedEncodingException e) { // Ignore } } } return buildLogRecord(payload, message); } private String buildLogRecord(String payload, HttpResponseMessage message) { StringBuilder builder = new StringBuilder("\n"); builder.append(message.getMethod()); builder.append("\n"); builder.append(message.getUrl().toString()); builder.append("\n"); if (payload != null) { builder.append(payload); builder.append("\n"); } builder.append(message.getStatusCode()); builder.append("\n"); builder.append(message.getResponse()); return builder.toString(); } }