All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.echobox.api.linkedin.client.DefaultVersionedLinkedInClient Maven / Gradle / Ivy

Go to download

ebx-linkedin-sdk is a pure Java LinkedIn API client. It implements the v2 LinkedIn API.

There is a newer version: 8.0.2
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.echobox.api.linkedin.client;

import com.echobox.api.linkedin.exception.LinkedInAPIException;
import com.echobox.api.linkedin.exception.LinkedInAccessTokenException;
import com.echobox.api.linkedin.exception.LinkedInException;
import com.echobox.api.linkedin.exception.LinkedInExceptionMapper;
import com.echobox.api.linkedin.exception.LinkedInGatewayTimeoutException;
import com.echobox.api.linkedin.exception.LinkedInInteralServerException;
import com.echobox.api.linkedin.exception.LinkedInJsonMappingException;
import com.echobox.api.linkedin.exception.LinkedInNetworkException;
import com.echobox.api.linkedin.exception.LinkedInOAuthException;
import com.echobox.api.linkedin.exception.LinkedInQueryParseException;
import com.echobox.api.linkedin.exception.LinkedInRateLimitException;
import com.echobox.api.linkedin.exception.LinkedInResourceNotFoundException;
import com.echobox.api.linkedin.exception.ResponseErrorJsonParsingException;
import com.echobox.api.linkedin.jsonmapper.DefaultJsonMapper;
import com.echobox.api.linkedin.jsonmapper.JsonMapper;
import com.echobox.api.linkedin.util.URLUtils;
import com.echobox.api.linkedin.util.ValidationUtils;
import com.echobox.api.linkedin.version.Version;
import com.eclipsesource.json.Json;
import com.eclipsesource.json.JsonObject;
import com.eclipsesource.json.JsonValue;
import com.eclipsesource.json.ParseException;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Default implementation of a LinkedIn versioned API client.
 * @author Kenneth Wong
 *
 */
public class DefaultVersionedLinkedInClient extends BaseLinkedInClient
    implements VersionedLinkedInClient {
  
  private static final Logger LOGGER =
      LoggerFactory.getLogger(DefaultVersionedLinkedInClient.class);

  /**
   * HTTP parameter names.
   */
  protected static final String METHOD_PARAM_NAME = "method";
  /**
   * The grant type parameter name.
   */
  protected static final String GRANT_TYPE_PARAM_NAME = "grant_type";
  /**
   * The code parameter name.
   */
  protected static final String CODE_PARAM_NAME = "code";
  /**
   * The redirect URI parameter name
   */
  protected static final String REDIRECT_URI_PARAM_NAME = "redirect_uri";
  /**
   * The client's ID parameter name
   */
  protected static final String CLIENT_ID_PARAM_NAME = "client_id";
  /**
   * The client's secret parameter name
   */
  protected static final String CLIENT_SECRET_PARAM_NAME = "client_secret";
  
  /**
   * Reserved "result format" parameter name.
   */
  protected static final String FORMAT_PARAM_NAME = "format";
  
  /**
   * API error response 'code' attribute name.
   */
  protected static final String ERROR_CODE_ATTRIBUTE_NAME = "serviceErrorCode";
  
  /**
   * API error response 'message' attribute name.
   */
  protected static final String ERROR_MESSAGE_ATTRIBUTE_NAME = "message";
  
  /**
   * Endpoint to fetch the access token query.
   */
  protected static final String ENDPOINT_ACCESS_TOKEN =
      "https://www.linkedin.com/oauth/v2/accessToken";
  
  /**
   * Reserved "start" parameter name.
   */
  protected static final String START = "start";
  
  /**
   * Reserved "end" parameter name.
   */
  protected static final String END = "end";
  
  /**
   * Request header to put API version
   */
  private static final String HEADER_NAME_VERSION = "Linkedin-Version";

  /**
   * Default LinkedIn-version header
   */
  public static final String DEFAULT_VERSIONED_MONTH = "202301";
  
  /**
   * Request header of protocol
   */
  private static final String HEADER_NAME_PROTOCOL = "X-Restli-Protocol-Version";
  
  /**
   * Default LinkedIn Protocol
   */
  private static final String DEFAULT_LINKEDIN_PROTOCOL = "2.0.0";
  
  /**
   * Graph API access token.
   */
  protected String accessToken;
  
  /**
   * Knows how to map Graph API exceptions to formal Java exception types.
   */
  protected LinkedInExceptionMapper linkedinExceptionMapper;
  
  /**
   * API endpoint URL.
   */
  protected static final String LINKEDIN_API_ENDPOINT_URL = "https://api.linkedin.com";
  
  /**
   * API endpoint URL.
   */
  protected static final String LINKEDIN_MEDIA_API_ENDPOINT_URL =
      "https://api.linkedin.com/media/upload";
  
  /**
   * Version of API endpoint.
   */
  protected Version apiVersion;
  
  /**
   * LinkedIn-Version header of the API to be used (format: YYYYMM)
   */
  protected String versionedMonth;
  
  /**
   * By default this is false, so real http DELETE is used
   */
  protected boolean httpDeleteFallback = false;
  
  private Map defaultHeaders;
  
  /**
   * Creates a LinkedIn API client with the given {@code accessToken}.
   *
   * @param accessToken
   *          A LinkedIn OAuth access token.
   * @throws GeneralSecurityException
   *          If the DefaultWebRequestor fails to initialise
   * @throws IOException
   *          If the DefaultWebRequestor fails to initialise
   */
  public DefaultVersionedLinkedInClient(String accessToken)
      throws GeneralSecurityException, IOException {
    this(accessToken, Version.VERSIONED);
  }
  
  /**
   * Creates a LinkedIn API client with the given {@code accessToken}.
   *
   * @param accessToken
   *          A LinkedIn OAuth access token.
   * @param apiVersion
   *          Version of the API endpoint
   * @throws GeneralSecurityException
   *          If the DefaultWebRequestor fails to initialise
   * @throws IOException
   *          If the DefaultWebRequestor fails to initialise
   */
  public DefaultVersionedLinkedInClient(String accessToken, Version apiVersion)
      throws GeneralSecurityException, IOException {
    this(new DefaultWebRequestor(accessToken), new DefaultJsonMapper(), apiVersion);
  }
  
  /**
   * Creates a LinkedIn API client with the given {@code apiVersion}.
   *
   * @param apiVersion
   *          Version of the api endpoint
   */
  public DefaultVersionedLinkedInClient(Version apiVersion) {
    this(null, new DefaultJsonMapper(), apiVersion, new DefaultLinkedInExceptionMapper());
  }
  
  /**
   * Creates a LinkedIn API client with the given {@code accessToken}.
   *
   * @param webRequestor
   *          The {@link WebRequestor} implementation to use for sending requests to the API
   *          endpoint.
   * @param jsonMapper
   *          The {@link JsonMapper} implementation to use for mapping API response JSON to Java
   *          objects.
   * @param apiVersion
   *          Version of the API endpoint
   */
  public DefaultVersionedLinkedInClient(WebRequestor webRequestor, JsonMapper jsonMapper,
      Version apiVersion) {
    this(webRequestor, jsonMapper, apiVersion, new DefaultLinkedInExceptionMapper());
    ValidationUtils.verifyParameterPresence("webRequestor", webRequestor);
  }
  
  /**
   * Creates a LinkedIn API client with the given {@code accessToken}.
   *
   * @param webRequestor
   *          The {@link WebRequestor} implementation to use for sending requests to the API
   *          endpoint.
   * @param jsonMapper
   *          The {@link JsonMapper} implementation to use for mapping API response JSON to Java
   *          objects.
   * @param apiVersion
   *          Version of the API endpoint
   * @param linkedinExceptionMapper
   *          Mapper class to handle LinkedIn exceptions
   */
  public DefaultVersionedLinkedInClient(WebRequestor webRequestor, JsonMapper jsonMapper,
      Version apiVersion, LinkedInExceptionMapper linkedinExceptionMapper) {
    this(webRequestor, jsonMapper, apiVersion, linkedinExceptionMapper, DEFAULT_VERSIONED_MONTH);
  }
  
  /**
   * Creates a LinkedIn API client with the given {@code accessToken}.
   *
   * @param webRequestor
   *          The {@link WebRequestor} implementation to use for sending requests to the API
   *          endpoint.
   * @param jsonMapper
   *          The {@link JsonMapper} implementation to use for mapping API response JSON to Java
   *          objects.
   * @param apiVersion
   *          Version of the API endpoint
   * @param linkedinExceptionMapper
   *          Mapper class to handle LinkedIn exceptions
   * @param versionedMonth
   *          LinkedIn-version of the API
   */
  public DefaultVersionedLinkedInClient(WebRequestor webRequestor, JsonMapper jsonMapper,
      Version apiVersion, LinkedInExceptionMapper linkedinExceptionMapper,
      String versionedMonth) {
    ValidationUtils.verifyParameterPresence("jsonMapper", jsonMapper);
    ValidationUtils.verifyParameterPresence("apiVersion", apiVersion);
    ValidationUtils.verifyParameterPresence("linkedinExceptionMapper", linkedinExceptionMapper);
    ValidationUtils.verifyParameterPresence("versionedMonth", versionedMonth);
    
    this.webRequestor = webRequestor;
    this.jsonMapper = jsonMapper;
    this.apiVersion = apiVersion;
    this.linkedinExceptionMapper = linkedinExceptionMapper;
    this.versionedMonth = versionedMonth;
    if (this.defaultHeaders == null) {
      this.defaultHeaders = new HashMap<>();
      this.defaultHeaders.put(HEADER_NAME_VERSION, versionedMonth);
    }
  }
  
  @Override
  public Version getVersion() {
    return apiVersion;
  }
  
  @Override
  public  T fetchObject(String object, Class objectType, Parameter... parameters) {
    ValidationUtils.verifyParameterPresence("object", object);
    ValidationUtils.verifyParameterPresence("objectType", objectType);
    WebRequestor.Response response = makeRequest(object, parameters);
    return jsonMapper.toJavaObject(response.getBody(), objectType);
  }
  
  @Override
  public  Connection fetchConnection(String connection, Class connectionType,
      Parameter... parameters) {
    ValidationUtils.verifyParameterPresence("connection", connection);
    ValidationUtils.verifyParameterPresence("connectionType", connectionType);
    final String fullEndpoint = createEndpointForApiCall(connection, false);
    
    List parametersToAdd = new ArrayList<>(Arrays.asList(parameters));
    Parameter[] queryParams = parametersToAdd.toArray(new Parameter[parametersToAdd.size()]);
    String parameterString = toParameterString(queryParams);
    final String finalParameterString =
        StringUtils.isBlank(parameterString) ? "" : ("?" + parameterString);
    WebRequestor.Response response = makeRequest(connection, queryParams);
    return new Connection<>(fullEndpoint + finalParameterString, this, response.getBody(),
        connectionType);
  }
  
  @Override
  public  Connection fetchConnectionPage(String connectionPageUrl, Class connectionType) {
    String connectionJson = makeRequestAndProcessResponseJSON(() -> {
      String pageURL = apiVersion.isSpecifyFormat()
          ? URLUtils.replaceOrAddQueryParameter(connectionPageUrl, "format", "json")
          : connectionPageUrl;
      return webRequestor.executeGet(pageURL, defaultHeaders);
    });
    
    return new Connection(connectionPageUrl, this, connectionJson, connectionType);
  }
  
  @Override
  public WebRequestor.Response publish(String connection, Object jsonBody,
      Parameter... parameters) {
    return makeRequest(connection, true, false, jsonBody,
        new ArrayList<>(), parameters);
  }
  
  @Override
  public  T publish(String connection, Class objectType, Object jsonBody,
      Parameter... parameters) {
    return publish(connection, objectType, jsonBody, new ArrayList<>(), parameters);
  }
  
  @Override
  public  T publish(String connection, Class objectType, Object jsonBody,
      List binaryAttachments, Parameter... parameters) {
    
    WebRequestor.Response response = makeRequest(connection, true, false, jsonBody,
        binaryAttachments, parameters);
    return jsonMapper.toJavaObject(response.getBody(), objectType);
  }
  
  @Override
  public  T publish(String connection, Class objectType, Object jsonBody,
      BinaryAttachment binaryAttachment, Parameter... parameters) {
    List attachments = null;
    if (binaryAttachment != null) {
      attachments = new ArrayList<>();
      attachments.add(binaryAttachment);
    }
    
    return publish(connection, objectType, jsonBody, attachments, parameters);
  }
  
  @Override
  public boolean deleteObject(String object, Parameter... parameters) {
    ValidationUtils.verifyParameterPresence("object", object);
    
    WebRequestor.Response response = makeRequest(object, false, true, null, null, parameters);
    String responseBody = response.getBody();
  
    try {
      JsonValue jObj = Json.parse(responseBody);
      if (jObj.isObject()) {
        if (jObj.asObject().get("result") != null) {
          return jObj.asObject().get("result").asString().contains("Successfully deleted");
        }
        if (jObj.asObject().get("success") != null) {
          return jObj.asObject().get("success").asBoolean();
        }
        return false;
      }
    } catch (ParseException jex) {
      if (LOGGER.isTraceEnabled()) {
        LOGGER.trace("no valid JSON returned while deleting a object, using returned String "
            + "instead", jex);
      }
    }
    
    return "true".equals(responseBody);
  }
  
  @Override
  public AccessToken obtainUserAccessToken(String appId, String appSecret, String redirectUri,
      String verificationCode) {
    ValidationUtils.verifyParameterPresence("appId", appId);
    ValidationUtils.verifyParameterPresence("appSecret", appSecret);
    ValidationUtils.verifyParameterPresence("redirectUri", redirectUri);
    ValidationUtils.verifyParameterPresence("verificationCode", verificationCode);
    
    try {
      this.webRequestor = new DefaultWebRequestor(appId, appSecret);
      
      Map headers = new HashMap<>();
      headers.put("Content-Type", "application/x-www-form-urlencoded");
      
      final WebRequestor.Response response = makeRequestFull(ENDPOINT_ACCESS_TOKEN,
          true, false, null,
          headers, Collections.emptyList(), Parameter.with(GRANT_TYPE_PARAM_NAME,
              "authorization_code"),
          Parameter.with(CODE_PARAM_NAME, verificationCode),
          Parameter.with(REDIRECT_URI_PARAM_NAME, redirectUri),
          Parameter.with(CLIENT_ID_PARAM_NAME, appId),
          Parameter.with(CLIENT_SECRET_PARAM_NAME, appSecret));
      
      return getAccessTokenFromResponse(response.getBody());
    } catch (Exception ex) {
      throw new LinkedInAccessTokenException(ex);
    }
  }
  
  private AccessToken getAccessTokenFromResponse(String response) {
    try {
      return getJsonMapper().toJavaObject(response, AccessToken.class);
    } catch (LinkedInJsonMappingException ex) {
      LOGGER.trace("could not map response to access token class try to fetch directly from String",
          ex);
      return AccessToken.fromQueryString(response);
    }
  }
  
  @Override
  public AccessToken obtainAppAccessToken(String appId, String appSecret) {
    throw new UnsupportedOperationException("Obtain user access token is not yet implemented");
  }
  
  @Override
  public JsonMapper getJsonMapper() {
    return jsonMapper;
  }
  
  @Override
  public WebRequestor getWebRequestor() {
    return webRequestor;
  }
  
  @Override
  protected String createEndpointForApiCall(String apiCall, boolean hasAttachment) {
    while (apiCall.startsWith("/")) {
      apiCall = apiCall.substring(1);
    }
    
    if (hasAttachment) {
      return getLinkedInMediaEndpointUrl();
    }
    
    String baseUrl = getLinkedInEndpointUrl();
    
    return String.format("%s/%s", baseUrl, apiCall);
  }
  
  /**
   * Returns the base endpoint URL for the LinkedIn API.
   *
   * @return The base endpoint URL for the LinkedIn API.
   */
  protected String getLinkedInEndpointUrl() {
    return LINKEDIN_API_ENDPOINT_URL + '/' + apiVersion.getUrlElement();
  }
  
  /**
   * Gets LinkedIn media endpoint URL
   *
   * @return LinkedIn media endpoint URL
   */
  protected String getLinkedInMediaEndpointUrl() {
    return LINKEDIN_MEDIA_API_ENDPOINT_URL;
  }
  
  /**
   * Coordinates the process of executing the API request GET/POST and processing the response we
   * receive from the endpoint.
   *
   * @param endpoint
   *          LinkedIn Graph API endpoint.
   * @param parameters
   *          Arbitrary number of parameters to send along to LinkedIn as part of the API call.
   * @return The JSON returned by LinkedIn for the API call.
   * @throws LinkedInException
   *           If an error occurs while making the LinkedIn API POST or processing the response.
   */
  protected WebRequestor.Response makeRequest(String endpoint, Parameter... parameters) {
    return makeRequest(endpoint, false, false, null, null, parameters);
  }
  
  /**
   * Coordinates the process of executing the API request GET/POST and processing the response we
   * receive from the endpoint, will append this endpoint on to the base LinkedIn API endpoint
   * before calling.
   *
   * @param endpoint
   *          LinkedIn Graph API endpoint.
   * @param executeAsPost
   *          {@code true} to execute the web request as a {@code POST}, {@code false} to execute
   *          as a {@code GET}.
   * @param executeAsDelete
   *          {@code true} to add a special 'treat this request as a {@code DELETE}' parameter.
   * @param jsonBody
   *          Post JSON body
   * @param binaryAttachments
   *          A list of binary files to include in a {@code POST} request. Pass {@code null} if no
   *          attachment should be sent.
   * @param parameters
   *          Arbitrary number of parameters to send along to LinkedIn as part of the API call.
   * @return The WebRequestor response returned by LinkedIn for the API call.
   * @throws LinkedInException
   *           If an error occurs while making the LinkedIn API POST or processing the response.
   */
  protected WebRequestor.Response makeRequest(String endpoint, final boolean executeAsPost,
      final boolean executeAsDelete, Object jsonBody,
      final List binaryAttachments, Parameter... parameters) {
    
    if (!endpoint.startsWith("/")) {
      endpoint = "/" + endpoint;
    }
    
    final String fullEndpoint = createEndpointForApiCall(endpoint,
        binaryAttachments != null && !binaryAttachments.isEmpty());

    return makeRequestFull(fullEndpoint, executeAsPost, executeAsDelete, jsonBody,
        defaultHeaders, binaryAttachments, parameters);
  }
  
  /**
   * Coordinates the process of executing the API request GET/POST and processing the response we
   * receive from a full endpoint, will call this endpoint directly without further processing
   * the URL.
   *
   * @param fullEndpoint
   *          LinkedIn Graph API endpoint.
   * @param executeAsPost
   *          {@code true} to execute the web request as a {@code POST}, {@code false} to execute
   *          as a {@code GET}.
   * @param executeAsDelete
   *          {@code true} to add a special 'treat this request as a {@code DELETE}' parameter.
   * @param jsonBody
   *          The POST JSON body
   * @param headers
   *          The headers for the request
   * @param binaryAttachments
   *          A list of binary files to include in a {@code POST} request. Pass {@code null} if no
   *          attachment should be sent.
   * @param parameters
   *          Arbitrary number of parameters to send along to LinkedIn as part of the API call.
   * @return The WebRequestor response returned by LinkedIn for the API call.
   */
  protected WebRequestor.Response makeRequestFull(String fullEndpoint, final boolean executeAsPost,
      final boolean executeAsDelete, Object jsonBody, Map headers,
      final List binaryAttachments, Parameter... parameters) {
    verifyParameterLegality(parameters);
    
    if (executeAsDelete && isHttpDeleteFallback()) {
      parameters = parametersWithAdditionalParameter(Parameter.with(METHOD_PARAM_NAME, "delete"),
          parameters);
    }
    
    String parameterString = toParameterString(parameters);
    final String finalParameterString =
        StringUtils.isBlank(parameterString) ? "" : ("?" + parameterString);
    
    return makeRequestAndProcessResponse(new Requestor() {
      /**
       * Make the request
       * @see DefaultVersionedLinkedInClient.Requestor#makeRequest()
       */
      @Override
      public WebRequestor.Response makeRequest() throws IOException {
        if (executeAsDelete && !isHttpDeleteFallback()) {
          return webRequestor.executeDelete(fullEndpoint + finalParameterString, headers);
        } else {
          return executeAsPost
              ? webRequestor.executePost(fullEndpoint, parameterString,
              jsonBody == null ? null : jsonMapper.toJson(jsonBody, true),
              headers,
              binaryAttachments == null ? null
                  : binaryAttachments.toArray(new BinaryAttachment[binaryAttachments.size()]))
              : webRequestor.executeGet(fullEndpoint + finalParameterString, headers);
        }
      }
    });
  }
  
  /**
   * Generate the parameter string to be included in the LinkedIn API request.
   *
   * @param parameters
   *          Arbitrary number of extra parameters to include in the request.
   * @return The parameter string to include in the LinkedIn API request.
   * @throws LinkedInJsonMappingException
   *           If an error occurs when building the parameter string.
   */
  protected String toParameterString(Parameter... parameters) {
    return toParameterString(apiVersion.isSpecifyFormat(), parameters);
  }
  
  /**
   * Generate the parameter string to be included in the LinkedIn API request.
   *
   * @param withJsonParameter
   *          add additional parameter format with type json
   * @param parameters
   *          Arbitrary number of extra parameters to include in the request.
   * @return The parameter string to include in the LinkedIn API request.
   * @throws LinkedInJsonMappingException
   *           If an error occurs when building the parameter string.
   */
  protected String toParameterString(boolean withJsonParameter, Parameter... parameters) {
    if (withJsonParameter) {
      parameters = parametersWithAdditionalParameter(Parameter.with(FORMAT_PARAM_NAME, "json"),
          parameters);
    }
    
    StringBuilder parameterStringBuilder = new StringBuilder();
    boolean first = true;
    
    for (Parameter parameter : parameters) {
      if (first) {
        first = false;
      } else {
        parameterStringBuilder.append("&");
      }
      
      parameterStringBuilder.append(URLUtils.urlEncode(parameter.name));
      parameterStringBuilder.append("=");
      parameterStringBuilder.append(urlEncodedValueForParameterName(parameter.value));
    }
    
    return parameterStringBuilder.toString();
  }
  
  /**
   * returns if the fallback post method (true) is used or the http delete
   * (false)
   *
   * @return a flag whether HTTP delete is a fallback
   */
  public boolean isHttpDeleteFallback() {
    return httpDeleteFallback;
  }
  
  /**
   * Make request and process response (json).
   *
   * @param requestor Requestor interface to make requests to the LinkedIn API
   * @return the JSON response
   */
  protected String makeRequestAndProcessResponseJSON(Requestor requestor) {
    WebRequestor.Response response = makeRequestAndProcessResponse(requestor);
    return response.getBody();
  }
  
  /**
   * Make request and process the response
   *
   * @param requestor Requestor interface to make requests to the LinkedIn API
   * @return the response
   */
  protected WebRequestor.Response makeRequestAndProcessResponse(Requestor requestor) {
    WebRequestor.Response response;
    
    // Perform a GET or POST to the API endpoint
    try {
      response = requestor.makeRequest();
    } catch (Exception t) {
      throw new LinkedInNetworkException("LinkedIn request failed", t);
    }
    
    // If we get any HTTP response code other than a 200 OK or 400 Bad Request
    // or 401 Not Authorized or 403 Forbidden or 404 Not Found or 500 Internal
    // Server Error or 302 Not Modified or 504 Gateway Timeout or 422 Unprocessable Entity or 429
    // Rate Limit throw an exception.
    if (HttpStatus.SC_OK != response.getStatusCode()
        && HttpStatus.SC_CREATED != response.getStatusCode()
        && HttpStatus.SC_NO_CONTENT != response.getStatusCode()
        && HttpStatus.SC_NOT_MODIFIED != response.getStatusCode()
        && HttpStatus.SC_BAD_REQUEST != response.getStatusCode()
        && HttpStatus.SC_UNAUTHORIZED != response.getStatusCode()
        && HttpStatus.SC_FORBIDDEN != response.getStatusCode()
        && HttpStatus.SC_NOT_FOUND != response.getStatusCode()
        && HttpStatus.SC_UNPROCESSABLE_ENTITY != response.getStatusCode()
        && HttpStatus.SC_TOO_MANY_REQUESTS != response.getStatusCode()
        && HttpStatus.SC_INTERNAL_SERVER_ERROR != response.getStatusCode()
        && HttpStatus.SC_GATEWAY_TIMEOUT != response.getStatusCode()) {
      throw new LinkedInNetworkException("LinkedIn request failed", response.getStatusCode());
    }
    
    String json = response.getBody();
    
    // If the response is 2XX then we do not need to throw an error response
    if (HttpStatus.SC_OK != response.getStatusCode()
        && HttpStatus.SC_CREATED != response.getStatusCode()
        && HttpStatus.SC_NO_CONTENT != response.getStatusCode()) {
      // If the response contained an error code, throw an exception.
      throwLinkedInResponseStatusExceptionIfNecessary(json, response.getStatusCode());
    }
    
    // If there was no response error information and this was a 500
    // error, something weird happened on LinkedIn's end. Bail.
    if (HttpStatus.SC_INTERNAL_SERVER_ERROR == response.getStatusCode()) {
      throw new LinkedInNetworkException("LinkedIn request failed", response.getStatusCode());
    }
    
    // If there was no response error information and this was a 401
    // error, something weird happened on LinkedIn's end. Assume it is a Oauth error.
    if (HttpStatus.SC_UNAUTHORIZED == response.getStatusCode()) {
      throw new LinkedInOAuthException("LinkedIn request failed", response.getStatusCode());
    }
    
    return response;
  }
  
  /**
   * Requestor interface to make requests to the LinkedIn API
   * @author Joanna
   *
   */
  protected interface Requestor {
    /**
     * Make a request
     * @return the received response
     * @throws IOException the IO exception
     */
    WebRequestor.Response makeRequest() throws IOException;
  }
  
  /**
   * Throws an exception if LinkedIn returned an error response.
   * This method extracts relevant information from the error JSON and throws an exception which
   * encapsulates it for end-user consumption.
   * For API errors:
   * If the {@code error} JSON field is present, we've got a response status error for this API
   * call.
   *
   * @param json
   *          The JSON returned by LinkedIn in response to an API call.
   * @param httpStatusCode
   *          The HTTP status code returned by the server, e.g. 500.
   * @throws LinkedInJsonMappingException
   *           If an error occurs while processing the JSON.
   */
  protected void throwLinkedInResponseStatusExceptionIfNecessary(String json,
      Integer httpStatusCode) {
    try {
      skipResponseStatusExceptionParsing(json);
      
      JsonObject errorObject = Json.parse(json).asObject();
      
      // If there's an Integer error code, pluck it out.
      Integer errorCode = errorObject.get(ERROR_CODE_ATTRIBUTE_NAME) != null
          ? Integer.parseInt(errorObject.get(ERROR_CODE_ATTRIBUTE_NAME).toString())
          : null;
      
      if (linkedinExceptionMapper != null) {
        throw linkedinExceptionMapper.exceptionForTypeAndMessage(errorCode, httpStatusCode,
            errorObject.getString(ERROR_MESSAGE_ATTRIBUTE_NAME, ""), false, errorObject);
      }
    } catch (ParseException e) {
      throw new LinkedInJsonMappingException("Unable to process the LinkedIn API response", e);
    } catch (ResponseErrorJsonParsingException ex) {
      if (LOGGER.isTraceEnabled()) {
        LOGGER.trace("caught ResponseErrorJsonParsingException - ignoring", ex);
      }
    }
  }
  
  /**
   * Implementation of {@code LinkedInExceptionMapper} that maps LinkedIn API exceptions.
   * @author Joanna
   */
  protected static class DefaultLinkedInExceptionMapper implements LinkedInExceptionMapper {
    @Override
    public LinkedInException exceptionForTypeAndMessage(Integer errorCode, Integer httpStatusCode,
        String message, Boolean isTransient, JsonObject rawError) {
      // Bad Request - client mistakes
      if (Integer.valueOf(HttpStatus.SC_BAD_REQUEST).equals(httpStatusCode)) {
        return new LinkedInQueryParseException(message, errorCode, httpStatusCode, rawError);
      }
      
      // Unauthorised
      if (Integer.valueOf(HttpStatus.SC_UNAUTHORIZED).equals(httpStatusCode)) {
        return new LinkedInOAuthException(message, errorCode, httpStatusCode, rawError);
      }
      
      // Resource not found
      if (Integer.valueOf(HttpStatus.SC_NOT_FOUND).equals(httpStatusCode)) {
        return new LinkedInResourceNotFoundException(message, errorCode, httpStatusCode,
            rawError);
      }
      
      // 429 Rate limit
      if (Integer.valueOf(HttpStatus.SC_TOO_MANY_REQUESTS).equals(httpStatusCode)) {
        return new LinkedInRateLimitException(message, errorCode, httpStatusCode,
            rawError);
      }
      
      // Internal Server Error
      if (Integer.valueOf(HttpStatus.SC_INTERNAL_SERVER_ERROR).equals(httpStatusCode)) {
        return new LinkedInInteralServerException(message, errorCode, httpStatusCode, rawError);
      }
      
      // Gateway timeout
      if (Integer.valueOf(HttpStatus.SC_GATEWAY_TIMEOUT).equals(httpStatusCode)) {
        return new LinkedInGatewayTimeoutException(message, errorCode, httpStatusCode, rawError);
      }
      
      // Don't recognize this exception type? Just go with the standard LinkedInAPIException.
      return new LinkedInAPIException(message, errorCode, httpStatusCode, rawError);
    }
  }
  
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy