Package com.restfb

Source Code of com.restfb.DefaultLegacyFacebookClient

/*
* Copyright (c) 2010-2011 Mark Allen.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package com.restfb;

import static com.restfb.util.StringUtils.isBlank;
import static com.restfb.util.StringUtils.toBytes;
import static com.restfb.util.StringUtils.trimToEmpty;
import static com.restfb.util.StringUtils.urlEncode;
import static java.net.HttpURLConnection.HTTP_OK;
import static java.util.logging.Level.INFO;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import com.restfb.WebRequestor.Response;
import com.restfb.exception.FacebookException;
import com.restfb.exception.FacebookJsonMappingException;
import com.restfb.exception.FacebookNetworkException;
import com.restfb.json.JsonArray;
import com.restfb.json.JsonException;
import com.restfb.json.JsonObject;

/**
* Default implementation of a <a
* href="http://developers.facebook.com/docs/reference/rest/">Legacy Facebook
* API</a> client.
*
* @author <a href="http://restfb.com">Mark Allen</a>
*/
public class DefaultLegacyFacebookClient extends BaseFacebookClient implements LegacyFacebookClient {
  /**
   * Facebook API key.
   */
  protected String apiKey;

  /**
   * Facebook application secret key.
   */
  protected String secretKey;

  /**
   * OAuth Access token.
   */
  protected String accessToken;

  /**
   * API endpoint URL.
   */
  protected static final String FACEBOOK_REST_ENDPOINT_URL = "https://api.facebook.com/restserver.php";

  /**
   * Read-only API endpoint URL.
   */
  protected static final String FACEBOOK_READ_ONLY_ENDPOINT_URL = "https://api-read.facebook.com/restserver.php";

  // Common parameter names/values that must be included in all API requests
  protected static final String METHOD_PARAM_NAME = "method";
  protected static final String FORMAT_PARAM_NAME = "format";
  protected static final String FORMAT_PARAM_VALUE = "json";

  // Common parameter names/values that must be included in all API requests
  // that do not use the new OAuth scheme.
  protected static final String API_KEY_PARAM_NAME = "api_key";
  protected static final String CALL_ID_PARAM_NAME = "call_id";
  protected static final String SIG_PARAM_NAME = "sig";
  protected static final String SESSION_KEY_PARAM_NAME = "session_key";
  protected static final String VERSION_PARAM_NAME = "v";
  protected static final String VERSION_PARAM_VALUE = "1.0";

  /**
   * Creates a Facebook API client with the given <a
   * href="http://developers.facebook.com/docs/guides/upgrade#oauth">OAuth
   * access token</a>.
   *
   * @param accessToken
   *          An OAuth access token.
   * @throws NullPointerException
   *           If {@code accessToken} is {@code null}.
   * @throws IllegalArgumentException
   *           If {@code accessToken} is a blank string.
   * @since 1.5
   */
  public DefaultLegacyFacebookClient(String accessToken) {
    this(accessToken, new DefaultWebRequestor(), new DefaultJsonMapper());
  }

  /**
   * Creates a Facebook API client with the given API key and secret key (Legacy
   * authentication).
   *
   * @param apiKey
   *          A Facebook API key.
   * @param secretKey
   *          A Facebook application secret key.
   * @throws NullPointerException
   *           If either parameter is {@code null}.
   * @throws IllegalArgumentException
   *           If either parameter is a blank string.
   * @deprecated You should use {@link #DefaultLegacyFacebookClient(String)}
   *             instead. Facebook is moving to OAuth and will stop supporting
   *             the old authentication scheme soon.
   */
  @Deprecated
  public DefaultLegacyFacebookClient(String apiKey, String secretKey) {
    this(apiKey, secretKey, new DefaultWebRequestor(), new DefaultJsonMapper());
  }

  /**
   * Creates a Facebook API client with the given API key, secret key,
   * {@code webRequestor}, and {@code jsonMapper} (Legacy authentication).
   *
   * @param apiKey
   *          A Facebook API key.
   * @param secretKey
   *          A Facebook application secret key.
   * @param webRequestor
   *          The {@link WebRequestor} implementation to use for {@code POST}ing
   *          to the API endpoint.
   * @param jsonMapper
   *          The {@link JsonMapper} implementation to use for mapping API
   *          response JSON to Java objects.
   * @throws NullPointerException
   *           If any parameter is {@code null}.
   * @throws IllegalArgumentException
   *           If either {@code apiKey} or {@code secretKey} is a blank string.
   * @deprecated You should use
   *             {@link #DefaultLegacyFacebookClient(String, WebRequestor, JsonMapper)}
   *             instead. Facebook is moving to OAuth and will stop supporting
   *             the old authentication scheme soon.
   */
  @Deprecated
  public DefaultLegacyFacebookClient(String apiKey, String secretKey, WebRequestor webRequestor, JsonMapper jsonMapper) {
    super();

    verifyParameterPresence("apiKey", apiKey);
    verifyParameterPresence("secretKey", secretKey);
    verifyParameterPresence("webRequestor", webRequestor);
    verifyParameterPresence("jsonMapper", jsonMapper);

    this.apiKey = apiKey.trim();
    this.secretKey = secretKey.trim();
    this.webRequestor = webRequestor;
    this.jsonMapper = jsonMapper;

    initializeIllegalParamNames();
  }

  /**
   * Creates a Facebook API client with the given <a
   * href="http://developers.facebook.com/docs/guides/upgrade#oauth">OAuth
   * access token</a>.
   *
   * @param accessToken
   *          An OAuth access token.
   * @param webRequestor
   *          The {@link WebRequestor} implementation to use for {@code POST}ing
   *          to the API endpoint.
   * @param jsonMapper
   *          The {@link JsonMapper} implementation to use for mapping API
   *          response JSON to Java objects.
   * @throws NullPointerException
   *           If any parameter is {@code null}.
   * @throws IllegalArgumentException
   *           If {@code accessToken} is a blank string.
   * @since 1.5
   */
  public DefaultLegacyFacebookClient(String accessToken, WebRequestor webRequestor, JsonMapper jsonMapper) {
    super();

    verifyParameterPresence("accessToken", accessToken);
    verifyParameterPresence("webRequestor", webRequestor);
    verifyParameterPresence("jsonMapper", jsonMapper);

    this.accessToken = accessToken.trim();
    this.webRequestor = webRequestor;
    this.jsonMapper = jsonMapper;

    initializeIllegalParamNames();
  }

  /**
   * @see com.restfb.LegacyFacebookClient#execute(java.lang.String,
   *      com.restfb.Parameter[])
   */
  @Override
  public void execute(String method, Parameter... parameters) {
    execute(method, (String) null, parameters);
  }

  /**
   * @see com.restfb.LegacyFacebookClient#execute(java.lang.String,
   *      java.lang.String, com.restfb.Parameter[])
   */
  @Override
  public void execute(String method, String sessionKey, Parameter... parameters) {
    makeRequest(method, sessionKey, parameters);
  }

  /**
   * @see com.restfb.LegacyFacebookClient#execute(java.lang.String,
   *      java.lang.Class, com.restfb.Parameter[])
   */
  @Override
  public <T> T execute(String method, Class<T> resultType, Parameter... parameters) {
    return execute(method, null, resultType, parameters);
  }

  /**
   * @see com.restfb.LegacyFacebookClient#execute(java.lang.String,
   *      java.lang.String, java.lang.Class, com.restfb.Parameter[])
   */
  @Override
  public <T> T execute(String method, String sessionKey, Class<T> resultType, Parameter... parameters) {
    return jsonMapper.toJavaObject(makeRequest(method, sessionKey, parameters), resultType);
  }

  /**
   * @see com.restfb.LegacyFacebookClient#executeForList(java.lang.String,
   *      java.lang.Class, com.restfb.Parameter[])
   */
  @Override
  public <T> List<T> executeForList(String method, Class<T> resultType, Parameter... parameters) {
    return executeForList(method, null, resultType, parameters);
  }

  /**
   * @see com.restfb.LegacyFacebookClient#executeForList(java.lang.String,
   *      java.lang.String, java.lang.Class, com.restfb.Parameter[])
   */
  @Override
  public <T> List<T> executeForList(String method, String sessionKey, Class<T> resultType, Parameter... parameters) {
    return jsonMapper.toJavaList(makeRequest(method, sessionKey, parameters), resultType);
  }

  /**
   * @see com.restfb.LegacyFacebookClient#executeMultiquery(java.util.Map,
   *      java.lang.Class, com.restfb.Parameter[])
   */
  @Override
  public <T> T executeMultiquery(Map<String, String> queries, Class<T> resultType, Parameter... additionalParameters) {
    return executeMultiquery(queries, resultType, additionalParameters);
  }

  /**
   * @see com.restfb.LegacyFacebookClient#executeMultiquery(java.util.Map,
   *      java.lang.String, java.lang.Class, com.restfb.Parameter[])
   */
  @Override
  public <T> T executeMultiquery(Map<String, String> queries, String sessionKey, Class<T> resultType,
      Parameter... additionalParameters) {
    List<Parameter> parameters = new ArrayList<Parameter>();
    parameters.add(Parameter.with("queries", queriesToJson(queries)));

    for (Parameter additionalParameter : additionalParameters) {
      if (additionalParameter.name.equals("queries"))
        throw new IllegalArgumentException("You cannot specify a parameter named 'queries' "
            + "because it's reserved for use by RestFB for this call. "
            + "Specify your queries in the Map that gets passed to this method.");

      parameters.add(additionalParameter);
    }

    JsonObject normalizedJson = new JsonObject();

    try {
      JsonArray jsonArray =
          new JsonArray(makeRequest("fql.multiquery", sessionKey, parameters.toArray(new Parameter[0])));

      for (int i = 0; i < jsonArray.length(); i++) {
        JsonObject jsonObject = jsonArray.getJsonObject(i);

        // For empty resultsets, Facebook will return an empty object instead of
        // an empty list. Hack around that here.
        JsonArray resultsArray =
            jsonObject.get("fql_result_set") instanceof JsonArray ? jsonObject.getJsonArray("fql_result_set")
                : new JsonArray();

        normalizedJson.put(jsonObject.getString("name"), resultsArray);
      }
    } catch (JsonException e) {
      throw new FacebookJsonMappingException("Unable to process fql.multiquery JSON response", e);
    }

    return jsonMapper.toJavaObject(normalizedJson.toString(), resultType);
  }

  /**
   * Coordinates the process of verifying and transforming API parameters,
   * executing the API POST, and processing the response we receive from the
   * endpoint.
   *
   * @param method
   *          Facebook API method name.
   * @param sessionKey
   *          Facebook API session key (can be {@code null} or a blank string).
   * @param parameters
   *          Arbitrary number of parameters to send along to Facebook as part
   *          of the API call.
   * @return The JSON returned by Facebook for the API call.
   * @throws FacebookException
   *           If an error occurs while making the Facebook API POST or
   *           processing the response.
   */
  protected String makeRequest(String method, String sessionKey, Parameter... parameters) {
    // Make sure we're not provided with any params that conflict with what
    // we're passing to FB internally
    verifyParameterLegality(parameters);

    // Turn the set of parameters into a string per the API spec
    String parametersAsString = toParameterString(method, sessionKey, parameters);

    Response response = null;

    // Perform a POST to the API endpoint
    try {
      response = webRequestor.executePost(createEndpointForApiCall(method, false), parametersAsString);
    } catch (Throwable t) {
      throw new FacebookNetworkException("Facebook POST failed", t);
    }

    if (logger.isLoggable(INFO))
      logger.info("Facebook responded with " + response);

    // If we get any HTTP response code other than a 200 OK, throw an exception
    if (HTTP_OK != response.getStatusCode())
      throw new FacebookNetworkException("Facebook POST failed", response.getStatusCode());

    String json = response.getBody();

    // If the response contained an error code, throw an exception
    throwLegacyFacebookResponseStatusExceptionIfNecessary(json);

    return json;
  }

  /**
   * Given basic request information, generate the parameter string to be
   * included in the Facebook API POST.
   *
   * @param method
   *          Facebook API method name.
   * @param sessionKey
   *          Facebook API session key (can be {@code null} or a blank string).
   * @param parameters
   *          Arbitrary number of extra parameters to include in the request.
   * @return The parameter string to include in the Facebook API POST.
   * @throws IllegalArgumentException
   *           If a session key is provided but we're using OAuth authentication
   *           instead.
   */
  protected String toParameterString(String method, String sessionKey, Parameter... parameters) {
    Map<String, String> sortedParameters = new TreeMap<String, String>();

    for (Parameter param : parameters)
      sortedParameters.put(param.name, param.value);

    sortedParameters.put(FORMAT_PARAM_NAME, FORMAT_PARAM_VALUE);
    sortedParameters.put(METHOD_PARAM_NAME, method);

    // New OAuth access token parameter is simple: just one value.
    // The legacy stuff requires a bunch more work.
    if (usesAccessTokenAuthentication()) {
      if (sessionKey != null)
        throw new IllegalArgumentException("If you're using the OAuth access token "
            + "for authentication, you cannot " + "specify a session key.");

      sortedParameters.put(ACCESS_TOKEN_PARAM_NAME, accessToken);
    } else {
      sortedParameters.put(API_KEY_PARAM_NAME, apiKey);
      sortedParameters.put(VERSION_PARAM_NAME, VERSION_PARAM_VALUE);
      sortedParameters.put(CALL_ID_PARAM_NAME, String.valueOf(System.currentTimeMillis()));

      if (!isBlank(sessionKey))
        sortedParameters.put(SESSION_KEY_PARAM_NAME, sessionKey);

      sortedParameters.put(SIG_PARAM_NAME, generateSignature(sortedParameters));
    }

    StringBuilder parameterStringBuilder = new StringBuilder();
    boolean first = true;

    for (Entry<String, String> entry : sortedParameters.entrySet()) {
      if (first)
        first = false;
      else
        parameterStringBuilder.append("&");

      parameterStringBuilder.append(urlEncode(entry.getKey()));
      parameterStringBuilder.append("=");
      parameterStringBuilder.append(usesAccessTokenAuthentication() ? urlEncodedValueForParameterName(entry.getKey(),
        entry.getValue()) : urlEncode(entry.getValue()));
    }

    return parameterStringBuilder.toString();
  }

  /**
   * Given a sorted map of parameter names to values, calculate and return the
   * Facebook API signature as defined by
   * http://wiki.developers.facebook.com/index.php/Verifying_The_Signature.
   *
   * @param sortedParameters
   *          Parameter name/value mappings, sorted alphabetically.
   * @return The Facebook API signature which matches the given parameter map.
   */
  protected String generateSignature(Map<String, String> sortedParameters) {
    StringBuilder parameterString = new StringBuilder();

    for (Entry<String, String> entry : sortedParameters.entrySet()) {
      parameterString.append(entry.getKey());
      parameterString.append("=");
      parameterString.append(entry.getValue());
    }

    parameterString.append(secretKey);
    return generateMd5(parameterString.toString());
  }

  /**
   * Generate an MD5 hash of the given {@code string}.
   *
   * @param string
   *          The string for which an MD5 hash is calculated.
   * @return The MD5 hash of the given {@code string}.
   * @throws IllegalStateException
   *           If MD5 hashing isn't supported on this platform (should never
   *           occur).
   */
  protected String generateMd5(String string) {
    try {
      MessageDigest messageDigest = MessageDigest.getInstance("MD5");
      byte[] bytes = toBytes(string);

      StringBuilder result = new StringBuilder();
      for (byte b : messageDigest.digest(bytes)) {
        result.append(Integer.toHexString((b & 0xf0) >>> 4));
        result.append(Integer.toHexString(b & 0x0f));
      }
      return result.toString();
    } catch (NoSuchAlgorithmException e) {
      // Should never happen
      throw new IllegalStateException("MD5 isn't available on this JVM", e);
    }
  }

  /**
   * Are we using OAuth access token authentication?
   *
   * @return {@code true} if we are, {@code false} if we're using the legacy
   *         authentication scheme.
   */
  protected boolean usesAccessTokenAuthentication() {
    return !isBlank(accessToken);
  }

  /**
   * @see com.restfb.BaseFacebookClient#createEndpointForApiCall(java.lang.String,boolean)
   */
  @Override
  protected String createEndpointForApiCall(String apiCall, boolean hasAttachment) {
    apiCall = trimToEmpty(apiCall).toLowerCase();
    return readOnlyApiCalls.contains(apiCall) ? getFacebookReadOnlyEndpointUrl() : getFacebookRestEndpointUrl();
  }

  /**
   * Initializes the set of illegal URL parameter names.
   */
  protected void initializeIllegalParamNames() {
    illegalParamNames.addAll(Arrays.asList(new String[] { API_KEY_PARAM_NAME, CALL_ID_PARAM_NAME, SIG_PARAM_NAME,
        METHOD_PARAM_NAME, SESSION_KEY_PARAM_NAME, FORMAT_PARAM_NAME, VERSION_PARAM_NAME, ACCESS_TOKEN_PARAM_NAME }));
  }

  /**
   * Returns the base endpoint URL for the Old REST API.
   *
   * @return The base endpoint URL for the Old REST API.
   */
  protected String getFacebookRestEndpointUrl() {
    return FACEBOOK_REST_ENDPOINT_URL;
  }

  /**
   * @see com.restfb.BaseFacebookClient#getFacebookReadOnlyEndpointUrl()
   */
  @Override
  protected String getFacebookReadOnlyEndpointUrl() {
    return FACEBOOK_READ_ONLY_ENDPOINT_URL;
  }
}
TOP

Related Classes of com.restfb.DefaultLegacyFacebookClient

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.