
com.echobox.api.linkedin.client.DefaultVersionedLinkedInClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ebx-linkedin-sdk Show documentation
Show all versions of ebx-linkedin-sdk Show documentation
ebx-linkedin-sdk is a pure Java LinkedIn API client. It implements the v2 LinkedIn API.
/*
* 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