package de.juplo.facebook;
+import de.juplo.facebook.token.SignedRequestAwareAuthorizationCodeAccessTokenProvider;
+import de.juplo.facebook.client.GraphApiErrorHandler;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
+++ /dev/null
-package de.juplo.facebook;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.client.ClientHttpResponse;
-import org.springframework.http.converter.HttpMessageConverter;
-import org.springframework.http.converter.HttpMessageNotReadableException;
-import org.springframework.security.oauth2.client.http.OAuth2ErrorHandler;
-import org.springframework.util.FileCopyUtils;
-import org.springframework.web.client.HttpMessageConverterExtractor;
-import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
-
-
-
-/**
- *
- * @author kai
- */
-public class GraphApiErrorHandler extends OAuth2ErrorHandler
-{
- private final OAuth2ErrorHandler errorHandler;
- private List<HttpMessageConverter<?>> messageConverters =
- new RestTemplate().getMessageConverters();
-
-
- public GraphApiErrorHandler(OAuth2ErrorHandler errorHandler)
- {
- super(null);
- this.errorHandler = errorHandler;
- }
-
-
- /**
- * @param messageConverters the messageConverters to set
- */
- @Override
- public void setMessageConverters(
- List<HttpMessageConverter<?>> messageConverters
- )
- {
- this.messageConverters = messageConverters;
- errorHandler.setMessageConverters(messageConverters);
- }
-
- @Override
- public boolean hasError(ClientHttpResponse response) throws IOException
- {
- return
- HttpStatus.Series.CLIENT_ERROR.equals(response.getStatusCode().series())
- || this.errorHandler.hasError(response);
- }
-
- @Override
- public void handleError(final ClientHttpResponse response) throws IOException
- {
- if (!HttpStatus.Series.CLIENT_ERROR.equals(response.getStatusCode().series()))
- {
- // We should only care about 400 level errors. Ex: A 500 server error shouldn't
- // be an oauth related error.
- errorHandler.handleError(response);
- }
- else
- {
- // Need to use buffered response because input stream may need to be consumed multiple times.
- ClientHttpResponse bufferedResponse = new ClientHttpResponse()
- {
- private byte[] lazyBody;
-
- @Override
- public HttpStatus getStatusCode() throws IOException
- {
- return response.getStatusCode();
- }
-
- @Override
- public synchronized InputStream getBody() throws IOException
- {
- if (lazyBody == null) {
- InputStream bodyStream = response.getBody();
- if (bodyStream != null) {
- lazyBody = FileCopyUtils.copyToByteArray(bodyStream);
- }
- else {
- lazyBody = new byte[0];
- }
- }
- return new ByteArrayInputStream(lazyBody);
- }
-
- @Override
- public HttpHeaders getHeaders()
- {
- return response.getHeaders();
- }
-
- @Override
- public String getStatusText() throws IOException
- {
- return response.getStatusText();
- }
-
- @Override
- public void close()
- {
- response.close();
- }
-
- @Override
- public int getRawStatusCode() throws IOException
- {
- return response.getRawStatusCode();
- }
- };
-
-
- HttpMessageConverterExtractor<GraphApiException> extractor =
- new HttpMessageConverterExtractor<>(
- GraphApiException.class,
- messageConverters
- );
-
- try
- {
- GraphApiException body = extractor.extractData(bufferedResponse);
- if (body != null)
- {
- // If we can get an OAuth2Exception already from the body, it is likely
- // to have more information than the header does, so just re-throw it
- // here.
- body.setHttpErrorCode(response.getRawStatusCode());
- throw body;
- }
- }
- catch (RestClientException|HttpMessageNotReadableException e)
- {
- // ignore
- }
-
- errorHandler.handleError(bufferedResponse);
- }
- }
-}
+++ /dev/null
-package de.juplo.facebook;
-
-import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
-
-/**
- * Base exception for Facebook Graph-Api exceptions.
- *
- * @author Kai Moritz
- */
-@org.codehaus.jackson.map.annotate.JsonDeserialize(using = GraphApiExceptionJackson1Deserializer.class)
-@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = GraphApiExceptionJackson2Deserializer.class)
-public class GraphApiException extends OAuth2Exception
-{
- private final String type;
- private final int code;
-
- private int httpErrorCode;
-
-
- public GraphApiException(String message, String type, int code)
- {
- super(message);
- this.type = type;
- this.code = code;
- }
-
-
- public String getType()
- {
- return type;
- }
-
- public int getCode()
- {
- return code;
- }
-
- @Override
- public int getHttpErrorCode()
- {
- return httpErrorCode == 0 ? super.getHttpErrorCode() : httpErrorCode;
- }
-
- public void setHttpErrorCode(int httpErrorCode)
- {
- this.httpErrorCode = httpErrorCode;
- }
-
- @Override
- public String toString()
- {
- StringBuilder builder = new StringBuilder();
- builder.append("{error:{\"message\":\"");
- builder.append(getMessage().replaceAll("\"", "\\\""));
- builder.append("\",\"type\":");
- builder.append(type.replaceAll("\"", "\\\""));
- builder.append("\",\"code\":");
- builder.append(code);
- builder.append("}}");
- return builder.toString();
- }
-}
+++ /dev/null
-package de.juplo.facebook;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import org.codehaus.jackson.JsonParser;
-import org.codehaus.jackson.JsonProcessingException;
-import org.codehaus.jackson.JsonToken;
-import org.codehaus.jackson.map.DeserializationContext;
-import org.codehaus.jackson.map.JsonDeserializer;
-
-/**
- * @author Kai Moritz
- */
-public class GraphApiExceptionJackson1Deserializer
- extends
- JsonDeserializer<GraphApiException>
-{
-
- @Override
- public GraphApiException deserialize(
- JsonParser jp,
- DeserializationContext ctxt
- )
- throws
- IOException,
- JsonProcessingException
- {
- JsonToken t = jp.getCurrentToken();
- if (t != JsonToken.START_OBJECT)
- return null;
-
- t = jp.nextToken();
- if (t != JsonToken.FIELD_NAME)
- return null;
-
- if (!jp.getCurrentName().equals("error"))
- return null;
-
- t = jp.nextToken();
- if (t != JsonToken.START_OBJECT)
- return null;
-
- String message = null, type = null;
- Integer code = null;
-
- t = jp.nextToken();
- Map<String, String> map = new HashMap<>();
- for (; t == JsonToken.FIELD_NAME; t = jp.nextToken())
- {
- // Must point to field name
- String fieldName = jp.getCurrentName();
- // And then the value...
- t = jp.nextToken();
-
- switch (t)
- {
- case VALUE_STRING:
- switch(fieldName.toLowerCase())
- {
- case "message":
- message = jp.getText();
- break;
- case "type":
- type = jp.getText();
- break;
- default:
- return null;
- }
- break;
- case VALUE_NUMBER_INT:
- if (!fieldName.equalsIgnoreCase("code"))
- return null;
- code = jp.getValueAsInt();
- break;
- default:
- return null;
- }
- }
-
- if (message == null || type == null || code == null)
- return null;
-
- switch (code)
- {
- case 1: return new UnknownErrorException();
- case 2: return new UnexpectedErrorException();
- case 21: return new PageMigratedException(message);
- case 100: return new UnsupportedGetRequestException();
- case 613: return new RateExceededException();
- default: return new GraphApiException(message, type, code);
- }
- }
-}
+++ /dev/null
-package de.juplo.facebook;
-
-
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.core.JsonToken;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-
-
-/**
- * @author Kai Moritz
- *
- */
-public class GraphApiExceptionJackson2Deserializer
- extends
- StdDeserializer<GraphApiException>
-{
- public GraphApiExceptionJackson2Deserializer()
- {
- super(GraphApiException.class);
- }
-
- @Override
- public GraphApiException deserialize(
- JsonParser jp,
- DeserializationContext ctxt
- )
- throws
- IOException,
- JsonProcessingException
- {
- JsonToken t = jp.getCurrentToken();
- if (t != JsonToken.START_OBJECT)
- return null;
-
- t = jp.nextToken();
- if (t != JsonToken.FIELD_NAME)
- return null;
-
- if (!jp.getCurrentName().equals("error"))
- return null;
-
- t = jp.nextToken();
- if (t != JsonToken.START_OBJECT)
- return null;
-
- String message = null, type = null;
- Integer code = null;
-
- t = jp.nextToken();
- Map<String, String> map = new HashMap<>();
- for (; t == JsonToken.FIELD_NAME; t = jp.nextToken())
- {
- // Must point to field name
- String fieldName = jp.getCurrentName();
- // And then the value...
- t = jp.nextToken();
-
- switch (t)
- {
- case VALUE_STRING:
- switch(fieldName.toLowerCase())
- {
- case "message":
- message = jp.getText();
- break;
- case "type":
- type = jp.getText();
- break;
- default:
- return null;
- }
- break;
- case VALUE_NUMBER_INT:
- if (!fieldName.equalsIgnoreCase("code"))
- return null;
- code = jp.getValueAsInt();
- break;
- default:
- return null;
- }
- }
-
- if (message == null || type == null || code == null)
- return null;
-
- switch (code)
- {
- case 1: return new UnknownErrorException();
- case 2: return new UnexpectedErrorException();
- case 21: return new PageMigratedException(message);
- case 100: return new UnsupportedGetRequestException();
- case 613: return new RateExceededException();
- default: return new GraphApiException(message, type, code);
- }
- }
-}
+++ /dev/null
-package de.juplo.facebook;
-
-
-/**
- *
- * @author kai
- */
-public abstract class GraphMethodException extends GraphApiException
-{
- public GraphMethodException(String message, int code)
- {
- super(message, "GraphMethodException", code);
- }
-}
+++ /dev/null
-package de.juplo.facebook;
-
-
-/**
- *
- * @author kai
- */
-public abstract class OAuthException extends GraphApiException
-{
- public OAuthException(String message, int code)
- {
- super(message, "OAuthException", code);
- }
-}
+++ /dev/null
-package de.juplo.facebook;
-
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-
-
-/**
- *
- * @author kai
- */
-public class PageMigratedException extends OAuthException
-{
- private final static Pattern pattern =
- Pattern.compile("Page ID ([0-9]+) was migrated to page ID ([0-9]+)");
-
- private final Long oldId, newId;
-
-
- public PageMigratedException(String message)
- {
- super(message, 21);
- Matcher matcher = pattern.matcher(message);
- if (!matcher.find())
- throw new RuntimeException("Could not parse migration-error: " + message);
- oldId = Long.parseLong(matcher.group(1));
- newId = Long.parseLong(matcher.group(2));
- }
-
-
- public Long getOldId()
- {
- return oldId;
- }
-
- public Long getNewId()
- {
- return newId;
- }
-}
+++ /dev/null
-package de.juplo.facebook;
-
-
-/**
- *
- * @author kai
- */
-public class RateExceededException extends OAuthException
-{
- public RateExceededException()
- {
- super("(#613) Calls to stream have exceeded the rate of 600 calls per 600 seconds.", 613);
- }
-}
+++ /dev/null
-package de.juplo.facebook;
-
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-import org.apache.commons.codec.binary.Base64;
-import org.codehaus.jackson.JsonNode;
-import org.codehaus.jackson.map.ObjectMapper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
-import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException;
-import org.springframework.security.oauth2.client.token.AccessTokenRequest;
-import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider;
-import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
-import org.springframework.security.oauth2.common.OAuth2AccessToken;
-
-
-/**
- * This class extends {@link AuthorizationCodeAccessTokenProvider} and adds
- * support for signed requests, which are issued by Facebook, if the Canvas-
- * or Tab-Page of a Facebook-App is accessed for the first time.
- *
- * @author Kai Moritz
- */
-public class SignedRequestAwareAuthorizationCodeAccessTokenProvider
- extends AuthorizationCodeAccessTokenProvider
-{
- private final Logger log =
- LoggerFactory.getLogger(SignedRequestAwareAuthorizationCodeAccessTokenProvider.class);
- private final static Pattern pattern =
- Pattern.compile("([a-zA-Z0-9_-]+)\\.([a-zA-Z0-9_-]+)");
-
- public final static String PARAM_SIGNED_REQUEST = "signed_request";
-
-
- private String secret;
- private ObjectMapper objectMapper;
-
-
- @Override
- public OAuth2AccessToken obtainAccessToken(
- OAuth2ProtectedResourceDetails details,
- AccessTokenRequest parameters
- )
- {
- try
- {
- return super.obtainAccessToken(details, parameters);
- }
- catch (UserRedirectRequiredException redirect)
- {
- log.debug("no valid access-token available: checking for signed request");
-
- if (!parameters.containsKey(PARAM_SIGNED_REQUEST))
- {
- log.info(
- "parameter " + PARAM_SIGNED_REQUEST + " is not present"
- );
- throw redirect;
- }
-
- String signed_request = parameters.get(PARAM_SIGNED_REQUEST).get(0);
-
- Matcher matcher = pattern.matcher(signed_request);
- if (!matcher.matches())
- {
- log.error("invalid signed_request: {}", signed_request);
- throw redirect;
- }
-
- String signature = matcher.group(1);
- String rawdata = matcher.group(2);
-
- String data;
- try
- {
- data = new String(Base64.decodeBase64(rawdata), "UTF-8");
- log.debug("JSON-data: {}", data);
- }
- catch (UnsupportedEncodingException e)
- {
- log.error("error while decoding data: {}", e.getMessage());
- throw redirect;
- }
-
- JsonNode json;
- try
- {
- json = objectMapper.readTree(data);
- }
- catch (IOException e)
- {
- log.error("error \"{}\" while parsing JSON-data: {}", e, data);
- throw redirect;
- }
-
- String algorithm = "";
- try
- {
- algorithm = json.get("algorithm").asText();
- }
- catch (NullPointerException e) {}
- if (algorithm.isEmpty())
- {
- log.error("field \"algorithm\" is missing: {}", data);
- throw redirect;
- }
- algorithm = algorithm.replaceAll("-", "");
-
- String check;
- try
- {
- SecretKeySpec key = new SecretKeySpec(secret.getBytes("UTF-8"), algorithm);
- Mac mac = Mac.getInstance(algorithm);
- mac.init(key);
- byte[] hmacData = mac.doFinal(rawdata.getBytes("UTF-8"));
- check = new String(Base64.encodeBase64URLSafe(hmacData), "UTF-8");
- }
- catch (
- UnsupportedEncodingException |
- NoSuchAlgorithmException |
- InvalidKeyException |
- IllegalStateException e
- )
- {
- log.error("signature check failed!", e);
- throw redirect;
- }
- if (!check.equals(signature))
- {
- log.error("signature does not match!");
- throw redirect;
- }
-
- /**
- * Extract additional information and store it in the token
- * See:
- * https://developers.facebook.com/docs/reference/login/signed-request/
- * TODO:
- * - Attribute "code"
- */
- Map<String,Object> additionalInformation = new HashMap<>();
- try
- {
- additionalInformation.put(
- "issued_at",
- new Date(json.get("issued_at").getLongValue()*1000L)
- );
- Map<String,Object> user = new HashMap<>();
- user.put(
- "country",
- json.get("user").get("country").asText()
- );
- user.put(
- "locale",
- json.get("user").get("locale").asText()
- );
- user.put(
- "age_min",
- json.get("user").get("age").get("min").getNumberValue()
- );
- if (json.get("user") != null && json.get("user").get("max") != null)
- user.put(
- "age_max",
- json.get("user").get("age").get("max").getNumberValue()
- );
- additionalInformation.put("user", user);
- if (json.get("app_data") != null)
- additionalInformation.put("app_data", json.get("app_data").asText());
- if (json.get("page") != null)
- {
- Map<String,Object> page = new HashMap<>();
- page.put("id", json.get("page").get("id").asText());
- page.put("liked", json.get("page").get("liked").asBoolean());
- page.put("admin", json.get("page").get("admin").asBoolean());
- additionalInformation.put("page", page);
- }
- }
- catch (NullPointerException e)
- {
- log.warn("expected additional data is missing: {}", data);
- }
-
- DefaultOAuth2AccessToken token = null;
- try
- {
- String value = json.get("oauth_token").asText();
- if (value.isEmpty())
- {
- log.error("field \"oauth_token\" is missing: {}", data);
- throw redirect;
- }
- token = new DefaultOAuth2AccessToken(value);
- token.setExpiration(new Date(json.get("expires").getLongValue()*1000L));
-
- additionalInformation.put(
- "user_id",
- json.get("user_id").asText()
- );
-
- token.setAdditionalInformation(additionalInformation);
- }
- catch (NullPointerException e)
- {
- if (token == null)
- {
- log.error("field \"oauth_token\" is missing: {}", data);
- throw redirect;
- }
- else
- log.warn("expected additional data is missing: {}", data);
- }
-
- return token;
- }
- }
-
-
- public String getSecret()
- {
- return secret;
- }
-
- public void setSecret(String secret)
- {
- this.secret = secret;
- }
-
- public ObjectMapper getObjectMapper()
- {
- return objectMapper;
- }
-
- public void setObjectMapper(ObjectMapper objectMapper)
- {
- this.objectMapper = objectMapper;
- }
-}
+++ /dev/null
-package de.juplo.facebook;
-
-
-import java.util.Map;
-import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException;
-
-
-
-/**
- *
- * @author kai
- */
-public class SignedRequestAwareUserRedirectRequiredException
- extends
- UserRedirectRequiredException
-{
- private final Map<String,Object> signedRequestData;
-
-
- public SignedRequestAwareUserRedirectRequiredException(
- String redirectUri,
- Map<String,String> requestParams,
- Map<String,Object> signedRequestData
- )
- {
- super(redirectUri, requestParams);
- this.signedRequestData = signedRequestData;
- }
-
-
- public Map<String, Object> getSignedRequestData()
- {
- return signedRequestData;
- }
-}
+++ /dev/null
-package de.juplo.facebook;
-
-
-/**
- *
- * @author kai
- */
-public class UnexpectedErrorException extends OAuthException
-{
- public UnexpectedErrorException()
- {
- super("An unexpected error has occurred. Please retry your request later.", 2);
- }
-}
+++ /dev/null
-package de.juplo.facebook;
-
-
-/**
- *
- * @author kai
- */
-public class UnknownErrorException extends OAuthException
-{
- public UnknownErrorException()
- {
- super("An unknown error has occurred.", 1);
- }
-}
+++ /dev/null
-package de.juplo.facebook;
-
-
-/**
- *
- * @author kai
- */
-public class UnsupportedGetRequestException extends GraphMethodException
-{
- public UnsupportedGetRequestException()
- {
- super("Unsupported get request.", 100);
- }
-}
--- /dev/null
+package de.juplo.facebook.client;
+
+import de.juplo.facebook.exceptions.GraphApiException;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.security.oauth2.client.http.OAuth2ErrorHandler;
+import org.springframework.util.FileCopyUtils;
+import org.springframework.web.client.HttpMessageConverterExtractor;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+
+
+
+/**
+ *
+ * @author kai
+ */
+public class GraphApiErrorHandler extends OAuth2ErrorHandler
+{
+ private final OAuth2ErrorHandler errorHandler;
+ private List<HttpMessageConverter<?>> messageConverters =
+ new RestTemplate().getMessageConverters();
+
+
+ public GraphApiErrorHandler(OAuth2ErrorHandler errorHandler)
+ {
+ super(null);
+ this.errorHandler = errorHandler;
+ }
+
+
+ /**
+ * @param messageConverters the messageConverters to set
+ */
+ @Override
+ public void setMessageConverters(
+ List<HttpMessageConverter<?>> messageConverters
+ )
+ {
+ this.messageConverters = messageConverters;
+ errorHandler.setMessageConverters(messageConverters);
+ }
+
+ @Override
+ public boolean hasError(ClientHttpResponse response) throws IOException
+ {
+ return
+ HttpStatus.Series.CLIENT_ERROR.equals(response.getStatusCode().series())
+ || this.errorHandler.hasError(response);
+ }
+
+ @Override
+ public void handleError(final ClientHttpResponse response) throws IOException
+ {
+ if (!HttpStatus.Series.CLIENT_ERROR.equals(response.getStatusCode().series()))
+ {
+ // We should only care about 400 level errors. Ex: A 500 server error shouldn't
+ // be an oauth related error.
+ errorHandler.handleError(response);
+ }
+ else
+ {
+ // Need to use buffered response because input stream may need to be consumed multiple times.
+ ClientHttpResponse bufferedResponse = new ClientHttpResponse()
+ {
+ private byte[] lazyBody;
+
+ @Override
+ public HttpStatus getStatusCode() throws IOException
+ {
+ return response.getStatusCode();
+ }
+
+ @Override
+ public synchronized InputStream getBody() throws IOException
+ {
+ if (lazyBody == null) {
+ InputStream bodyStream = response.getBody();
+ if (bodyStream != null) {
+ lazyBody = FileCopyUtils.copyToByteArray(bodyStream);
+ }
+ else {
+ lazyBody = new byte[0];
+ }
+ }
+ return new ByteArrayInputStream(lazyBody);
+ }
+
+ @Override
+ public HttpHeaders getHeaders()
+ {
+ return response.getHeaders();
+ }
+
+ @Override
+ public String getStatusText() throws IOException
+ {
+ return response.getStatusText();
+ }
+
+ @Override
+ public void close()
+ {
+ response.close();
+ }
+
+ @Override
+ public int getRawStatusCode() throws IOException
+ {
+ return response.getRawStatusCode();
+ }
+ };
+
+
+ HttpMessageConverterExtractor<GraphApiException> extractor =
+ new HttpMessageConverterExtractor<>(
+ GraphApiException.class,
+ messageConverters
+ );
+
+ try
+ {
+ GraphApiException body = extractor.extractData(bufferedResponse);
+ if (body != null)
+ {
+ // If we can get an OAuth2Exception already from the body, it is likely
+ // to have more information than the header does, so just re-throw it
+ // here.
+ body.setHttpErrorCode(response.getRawStatusCode());
+ throw body;
+ }
+ }
+ catch (RestClientException|HttpMessageNotReadableException e)
+ {
+ // ignore
+ }
+
+ errorHandler.handleError(bufferedResponse);
+ }
+ }
+}
--- /dev/null
+package de.juplo.facebook.exceptions;
+
+import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
+
+/**
+ * Base exception for Facebook Graph-Api exceptions.
+ *
+ * @author Kai Moritz
+ */
+@org.codehaus.jackson.map.annotate.JsonDeserialize(using = GraphApiExceptionJackson1Deserializer.class)
+@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = GraphApiExceptionJackson2Deserializer.class)
+public class GraphApiException extends OAuth2Exception
+{
+ private final String type;
+ private final int code;
+
+ private int httpErrorCode;
+
+
+ public GraphApiException(String message, String type, int code)
+ {
+ super(message);
+ this.type = type;
+ this.code = code;
+ }
+
+
+ public String getType()
+ {
+ return type;
+ }
+
+ public int getCode()
+ {
+ return code;
+ }
+
+ @Override
+ public int getHttpErrorCode()
+ {
+ return httpErrorCode == 0 ? super.getHttpErrorCode() : httpErrorCode;
+ }
+
+ public void setHttpErrorCode(int httpErrorCode)
+ {
+ this.httpErrorCode = httpErrorCode;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("{error:{\"message\":\"");
+ builder.append(getMessage().replaceAll("\"", "\\\""));
+ builder.append("\",\"type\":");
+ builder.append(type.replaceAll("\"", "\\\""));
+ builder.append("\",\"code\":");
+ builder.append(code);
+ builder.append("}}");
+ return builder.toString();
+ }
+}
--- /dev/null
+package de.juplo.facebook.exceptions;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.JsonToken;
+import org.codehaus.jackson.map.DeserializationContext;
+import org.codehaus.jackson.map.JsonDeserializer;
+
+/**
+ * @author Kai Moritz
+ */
+public class GraphApiExceptionJackson1Deserializer
+ extends
+ JsonDeserializer<GraphApiException>
+{
+
+ @Override
+ public GraphApiException deserialize(
+ JsonParser jp,
+ DeserializationContext ctxt
+ )
+ throws
+ IOException,
+ JsonProcessingException
+ {
+ JsonToken t = jp.getCurrentToken();
+ if (t != JsonToken.START_OBJECT)
+ return null;
+
+ t = jp.nextToken();
+ if (t != JsonToken.FIELD_NAME)
+ return null;
+
+ if (!jp.getCurrentName().equals("error"))
+ return null;
+
+ t = jp.nextToken();
+ if (t != JsonToken.START_OBJECT)
+ return null;
+
+ String message = null, type = null;
+ Integer code = null;
+
+ t = jp.nextToken();
+ Map<String, String> map = new HashMap<>();
+ for (; t == JsonToken.FIELD_NAME; t = jp.nextToken())
+ {
+ // Must point to field name
+ String fieldName = jp.getCurrentName();
+ // And then the value...
+ t = jp.nextToken();
+
+ switch (t)
+ {
+ case VALUE_STRING:
+ switch(fieldName.toLowerCase())
+ {
+ case "message":
+ message = jp.getText();
+ break;
+ case "type":
+ type = jp.getText();
+ break;
+ default:
+ return null;
+ }
+ break;
+ case VALUE_NUMBER_INT:
+ if (!fieldName.equalsIgnoreCase("code"))
+ return null;
+ code = jp.getValueAsInt();
+ break;
+ default:
+ return null;
+ }
+ }
+
+ if (message == null || type == null || code == null)
+ return null;
+
+ switch (code)
+ {
+ case 1: return new UnknownErrorException();
+ case 2: return new UnexpectedErrorException();
+ case 21: return new PageMigratedException(message);
+ case 100: return new UnsupportedGetRequestException();
+ case 613: return new RateExceededException();
+ default: return new GraphApiException(message, type, code);
+ }
+ }
+}
--- /dev/null
+package de.juplo.facebook.exceptions;
+
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * @author Kai Moritz
+ *
+ */
+public class GraphApiExceptionJackson2Deserializer
+ extends
+ StdDeserializer<GraphApiException>
+{
+ public GraphApiExceptionJackson2Deserializer()
+ {
+ super(GraphApiException.class);
+ }
+
+ @Override
+ public GraphApiException deserialize(
+ JsonParser jp,
+ DeserializationContext ctxt
+ )
+ throws
+ IOException,
+ JsonProcessingException
+ {
+ JsonToken t = jp.getCurrentToken();
+ if (t != JsonToken.START_OBJECT)
+ return null;
+
+ t = jp.nextToken();
+ if (t != JsonToken.FIELD_NAME)
+ return null;
+
+ if (!jp.getCurrentName().equals("error"))
+ return null;
+
+ t = jp.nextToken();
+ if (t != JsonToken.START_OBJECT)
+ return null;
+
+ String message = null, type = null;
+ Integer code = null;
+
+ t = jp.nextToken();
+ Map<String, String> map = new HashMap<>();
+ for (; t == JsonToken.FIELD_NAME; t = jp.nextToken())
+ {
+ // Must point to field name
+ String fieldName = jp.getCurrentName();
+ // And then the value...
+ t = jp.nextToken();
+
+ switch (t)
+ {
+ case VALUE_STRING:
+ switch(fieldName.toLowerCase())
+ {
+ case "message":
+ message = jp.getText();
+ break;
+ case "type":
+ type = jp.getText();
+ break;
+ default:
+ return null;
+ }
+ break;
+ case VALUE_NUMBER_INT:
+ if (!fieldName.equalsIgnoreCase("code"))
+ return null;
+ code = jp.getValueAsInt();
+ break;
+ default:
+ return null;
+ }
+ }
+
+ if (message == null || type == null || code == null)
+ return null;
+
+ switch (code)
+ {
+ case 1: return new UnknownErrorException();
+ case 2: return new UnexpectedErrorException();
+ case 21: return new PageMigratedException(message);
+ case 100: return new UnsupportedGetRequestException();
+ case 613: return new RateExceededException();
+ default: return new GraphApiException(message, type, code);
+ }
+ }
+}
--- /dev/null
+package de.juplo.facebook.exceptions;
+
+
+/**
+ *
+ * @author kai
+ */
+public abstract class GraphMethodException extends GraphApiException
+{
+ public GraphMethodException(String message, int code)
+ {
+ super(message, "GraphMethodException", code);
+ }
+}
--- /dev/null
+package de.juplo.facebook.exceptions;
+
+
+/**
+ *
+ * @author kai
+ */
+public abstract class OAuthException extends GraphApiException
+{
+ public OAuthException(String message, int code)
+ {
+ super(message, "OAuthException", code);
+ }
+}
--- /dev/null
+package de.juplo.facebook.exceptions;
+
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+
+/**
+ *
+ * @author kai
+ */
+public class PageMigratedException extends OAuthException
+{
+ private final static Pattern pattern =
+ Pattern.compile("Page ID ([0-9]+) was migrated to page ID ([0-9]+)");
+
+ private final Long oldId, newId;
+
+
+ public PageMigratedException(String message)
+ {
+ super(message, 21);
+ Matcher matcher = pattern.matcher(message);
+ if (!matcher.find())
+ throw new RuntimeException("Could not parse migration-error: " + message);
+ oldId = Long.parseLong(matcher.group(1));
+ newId = Long.parseLong(matcher.group(2));
+ }
+
+
+ public Long getOldId()
+ {
+ return oldId;
+ }
+
+ public Long getNewId()
+ {
+ return newId;
+ }
+}
--- /dev/null
+package de.juplo.facebook.exceptions;
+
+
+/**
+ *
+ * @author kai
+ */
+public class RateExceededException extends OAuthException
+{
+ public RateExceededException()
+ {
+ super("(#613) Calls to stream have exceeded the rate of 600 calls per 600 seconds.", 613);
+ }
+}
--- /dev/null
+package de.juplo.facebook.exceptions;
+
+
+import java.util.Map;
+import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException;
+
+
+
+/**
+ *
+ * @author kai
+ */
+public class SignedRequestAwareUserRedirectRequiredException
+ extends
+ UserRedirectRequiredException
+{
+ private final Map<String,Object> signedRequestData;
+
+
+ public SignedRequestAwareUserRedirectRequiredException(
+ String redirectUri,
+ Map<String,String> requestParams,
+ Map<String,Object> signedRequestData
+ )
+ {
+ super(redirectUri, requestParams);
+ this.signedRequestData = signedRequestData;
+ }
+
+
+ public Map<String, Object> getSignedRequestData()
+ {
+ return signedRequestData;
+ }
+}
--- /dev/null
+package de.juplo.facebook.exceptions;
+
+
+/**
+ *
+ * @author kai
+ */
+public class UnexpectedErrorException extends OAuthException
+{
+ public UnexpectedErrorException()
+ {
+ super("An unexpected error has occurred. Please retry your request later.", 2);
+ }
+}
--- /dev/null
+package de.juplo.facebook.exceptions;
+
+
+/**
+ *
+ * @author kai
+ */
+public class UnknownErrorException extends OAuthException
+{
+ public UnknownErrorException()
+ {
+ super("An unknown error has occurred.", 1);
+ }
+}
--- /dev/null
+package de.juplo.facebook.exceptions;
+
+
+/**
+ *
+ * @author kai
+ */
+public class UnsupportedGetRequestException extends GraphMethodException
+{
+ public UnsupportedGetRequestException()
+ {
+ super("Unsupported get request.", 100);
+ }
+}
--- /dev/null
+package de.juplo.facebook.token;
+
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import org.apache.commons.codec.binary.Base64;
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
+import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException;
+import org.springframework.security.oauth2.client.token.AccessTokenRequest;
+import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider;
+import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
+import org.springframework.security.oauth2.common.OAuth2AccessToken;
+
+
+/**
+ * This class extends {@link AuthorizationCodeAccessTokenProvider} and adds
+ * support for signed requests, which are issued by Facebook, if the Canvas-
+ * or Tab-Page of a Facebook-App is accessed for the first time.
+ *
+ * @author Kai Moritz
+ */
+public class SignedRequestAwareAuthorizationCodeAccessTokenProvider
+ extends AuthorizationCodeAccessTokenProvider
+{
+ private final Logger log =
+ LoggerFactory.getLogger(SignedRequestAwareAuthorizationCodeAccessTokenProvider.class);
+ private final static Pattern pattern =
+ Pattern.compile("([a-zA-Z0-9_-]+)\\.([a-zA-Z0-9_-]+)");
+
+ public final static String PARAM_SIGNED_REQUEST = "signed_request";
+
+
+ private String secret;
+ private ObjectMapper objectMapper;
+
+
+ @Override
+ public OAuth2AccessToken obtainAccessToken(
+ OAuth2ProtectedResourceDetails details,
+ AccessTokenRequest parameters
+ )
+ {
+ try
+ {
+ return super.obtainAccessToken(details, parameters);
+ }
+ catch (UserRedirectRequiredException redirect)
+ {
+ log.debug("no valid access-token available: checking for signed request");
+
+ if (!parameters.containsKey(PARAM_SIGNED_REQUEST))
+ {
+ log.info(
+ "parameter " + PARAM_SIGNED_REQUEST + " is not present"
+ );
+ throw redirect;
+ }
+
+ String signed_request = parameters.get(PARAM_SIGNED_REQUEST).get(0);
+
+ Matcher matcher = pattern.matcher(signed_request);
+ if (!matcher.matches())
+ {
+ log.error("invalid signed_request: {}", signed_request);
+ throw redirect;
+ }
+
+ String signature = matcher.group(1);
+ String rawdata = matcher.group(2);
+
+ String data;
+ try
+ {
+ data = new String(Base64.decodeBase64(rawdata), "UTF-8");
+ log.debug("JSON-data: {}", data);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ log.error("error while decoding data: {}", e.getMessage());
+ throw redirect;
+ }
+
+ JsonNode json;
+ try
+ {
+ json = objectMapper.readTree(data);
+ }
+ catch (IOException e)
+ {
+ log.error("error \"{}\" while parsing JSON-data: {}", e, data);
+ throw redirect;
+ }
+
+ String algorithm = "";
+ try
+ {
+ algorithm = json.get("algorithm").asText();
+ }
+ catch (NullPointerException e) {}
+ if (algorithm.isEmpty())
+ {
+ log.error("field \"algorithm\" is missing: {}", data);
+ throw redirect;
+ }
+ algorithm = algorithm.replaceAll("-", "");
+
+ String check;
+ try
+ {
+ SecretKeySpec key = new SecretKeySpec(secret.getBytes("UTF-8"), algorithm);
+ Mac mac = Mac.getInstance(algorithm);
+ mac.init(key);
+ byte[] hmacData = mac.doFinal(rawdata.getBytes("UTF-8"));
+ check = new String(Base64.encodeBase64URLSafe(hmacData), "UTF-8");
+ }
+ catch (
+ UnsupportedEncodingException |
+ NoSuchAlgorithmException |
+ InvalidKeyException |
+ IllegalStateException e
+ )
+ {
+ log.error("signature check failed!", e);
+ throw redirect;
+ }
+ if (!check.equals(signature))
+ {
+ log.error("signature does not match!");
+ throw redirect;
+ }
+
+ /**
+ * Extract additional information and store it in the token
+ * See:
+ * https://developers.facebook.com/docs/reference/login/signed-request/
+ * TODO:
+ * - Attribute "code"
+ */
+ Map<String,Object> additionalInformation = new HashMap<>();
+ try
+ {
+ additionalInformation.put(
+ "issued_at",
+ new Date(json.get("issued_at").getLongValue()*1000L)
+ );
+ Map<String,Object> user = new HashMap<>();
+ user.put(
+ "country",
+ json.get("user").get("country").asText()
+ );
+ user.put(
+ "locale",
+ json.get("user").get("locale").asText()
+ );
+ user.put(
+ "age_min",
+ json.get("user").get("age").get("min").getNumberValue()
+ );
+ if (json.get("user") != null && json.get("user").get("max") != null)
+ user.put(
+ "age_max",
+ json.get("user").get("age").get("max").getNumberValue()
+ );
+ additionalInformation.put("user", user);
+ if (json.get("app_data") != null)
+ additionalInformation.put("app_data", json.get("app_data").asText());
+ if (json.get("page") != null)
+ {
+ Map<String,Object> page = new HashMap<>();
+ page.put("id", json.get("page").get("id").asText());
+ page.put("liked", json.get("page").get("liked").asBoolean());
+ page.put("admin", json.get("page").get("admin").asBoolean());
+ additionalInformation.put("page", page);
+ }
+ }
+ catch (NullPointerException e)
+ {
+ log.warn("expected additional data is missing: {}", data);
+ }
+
+ DefaultOAuth2AccessToken token = null;
+ try
+ {
+ String value = json.get("oauth_token").asText();
+ if (value.isEmpty())
+ {
+ log.error("field \"oauth_token\" is missing: {}", data);
+ throw redirect;
+ }
+ token = new DefaultOAuth2AccessToken(value);
+ token.setExpiration(new Date(json.get("expires").getLongValue()*1000L));
+
+ additionalInformation.put(
+ "user_id",
+ json.get("user_id").asText()
+ );
+
+ token.setAdditionalInformation(additionalInformation);
+ }
+ catch (NullPointerException e)
+ {
+ if (token == null)
+ {
+ log.error("field \"oauth_token\" is missing: {}", data);
+ throw redirect;
+ }
+ else
+ log.warn("expected additional data is missing: {}", data);
+ }
+
+ return token;
+ }
+ }
+
+
+ public String getSecret()
+ {
+ return secret;
+ }
+
+ public void setSecret(String secret)
+ {
+ this.secret = secret;
+ }
+
+ public ObjectMapper getObjectMapper()
+ {
+ return objectMapper;
+ }
+
+ public void setObjectMapper(ObjectMapper objectMapper)
+ {
+ this.objectMapper = objectMapper;
+ }
+}
+++ /dev/null
-package de.juplo.facebook;
-
-import java.util.Date;
-import java.util.Map;
-import java.util.Set;
-import javax.annotation.Resource;
-import static org.junit.Assert.*;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.http.HttpStatus;
-import org.springframework.security.access.AccessDeniedException;
-import org.springframework.security.oauth2.client.OAuth2RestTemplate;
-import org.springframework.security.oauth2.client.http.OAuth2ErrorHandler;
-import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
-import org.springframework.security.oauth2.client.resource.UserApprovalRequiredException;
-import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException;
-import org.springframework.security.oauth2.client.token.AccessTokenProvider;
-import org.springframework.security.oauth2.client.token.AccessTokenRequest;
-import org.springframework.security.oauth2.common.OAuth2AccessToken;
-import static org.springframework.security.oauth2.common.OAuth2AccessToken.OAUTH2_TYPE;
-import org.springframework.security.oauth2.common.OAuth2RefreshToken;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-import org.springframework.web.client.HttpClientErrorException;
-
-
-
-/**
- *
- * @author kai
- */
-@RunWith(SpringJUnit4ClassRunner.class)
-@ContextConfiguration(
- locations = {
- "classpath:/spring/test-facebook-error-handler.xml"
- })
-public class GraphApiErrorHandlerTest
-{
- private static final Logger log =
- LoggerFactory.getLogger(GraphApiErrorHandlerTest.class);
-
- @Resource
- private OAuth2RestTemplate clientTemplate;
-
- private MockClientHttpRequestFactory requestFactory;
-
-
- @Test
- public void testError1()
- {
- log.info("testError1");
-
-
- requestFactory.setBody(
- "{\n" +
- " \"error\":\n" +
- " {\n" +
- " \"message\": \"An unknown error has occurred.\",\n" +
- " \"type\": \"OAuthException\",\n" +
- " \"code\": 1\n" +
- " }\n" +
- "}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(UnknownErrorException e)
- {
- log.debug("{}", e.toString());
- assertEquals("invalid_request", e.getOAuth2ErrorCode());
- assertEquals(1, e.getCode());
- assertEquals("An unknown error has occurred.", e.getMessage());
- assertEquals("OAuthException", e.getType());
- }
- }
-
- @Test
- public void testError2()
- {
- log.info("testError2");
-
-
- requestFactory.setBody(
- "{\n" +
- " \"error\":\n" +
- " {\n" +
- " \"message\": \"An unexpected error has occurred. Please retry your request later.\",\n" +
- " \"type\": \"OAuthException\",\n" +
- " \"code\": 2\n" +
- " }\n" +
- "}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(UnexpectedErrorException e)
- {
- log.debug("{}", e.toString());
- assertEquals("invalid_request", e.getOAuth2ErrorCode());
- assertEquals(2, e.getCode());
- assertEquals("An unexpected error has occurred. Please retry your request later.", e.getMessage());
- assertEquals("OAuthException", e.getType());
- }
- }
-
- @Test
- public void testError21()
- {
- log.info("testError21");
-
-
- requestFactory.setBody(
- "{\n" +
- " \"error\":\n" +
- " {\n" +
- " \"message\": \"(#21) Page ID 590408587650316 was migrated to page ID 1421620791415603. Please update your API calls to the new ID\",\n" +
- " \"type\": \"OAuthException\",\n" +
- " \"code\": 21\n" +
- " }\n" +
- "}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(PageMigratedException e)
- {
- log.debug("{}", e.toString());
- assertEquals("invalid_request", e.getOAuth2ErrorCode());
- assertEquals(21, e.getCode());
- assertEquals("(#21) Page ID 590408587650316 was migrated to page ID 1421620791415603. Please update your API calls to the new ID", e.getMessage());
- assertEquals("OAuthException", e.getType());
- }
- }
-
- @Test
- public void testError100()
- {
- log.info("testError100");
-
-
- requestFactory.setBody(
- "{\n" +
- " \"error\":\n" +
- " {\n" +
- " \"message\": \"Unsupported get request.\",\n" +
- " \"type\": \"GraphMethodException\",\n" +
- " \"code\": 100\n" +
- " }\n" +
- "}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(UnsupportedGetRequestException e)
- {
- log.debug("{}", e.toString());
- assertEquals("invalid_request", e.getOAuth2ErrorCode());
- assertEquals(100, e.getCode());
- assertEquals("Unsupported get request.", e.getMessage());
- assertEquals("GraphMethodException", e.getType());
- }
- }
-
- @Test
- public void testError613()
- {
- log.info("testError613");
-
-
- requestFactory.setBody(
- "{\n" +
- " \"error\":\n" +
- " {\n" +
- " \"message\": \"(#613) Calls to stream have exceeded the rate of 600 calls per 600 seconds.\",\n" +
- " \"type\": \"OAuthException\",\n" +
- " \"code\": 613\n" +
- " }\n" +
- "}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(RateExceededException e)
- {
- log.debug("{}", e.toString());
- assertEquals("invalid_request", e.getOAuth2ErrorCode());
- assertEquals(613, e.getCode());
- assertEquals("(#613) Calls to stream have exceeded the rate of 600 calls per 600 seconds.", e.getMessage());
- assertEquals("OAuthException", e.getType());
- }
- }
-
- @Test
- public void testUnmappedError()
- {
- log.info("testUnmappedError");
-
-
- requestFactory.setBody(
- "{\n" +
- " \"error\":\n" +
- " {\n" +
- " \"message\": \"This error does not exist.\",\n" +
- " \"type\": \"NonexistentException\",\n" +
- " \"code\": 999999999\n" +
- " }\n" +
- "}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(GraphApiException e)
- {
- log.debug("{}", e.toString());
- assertEquals("invalid_request", e.getOAuth2ErrorCode());
- assertEquals(999999999, e.getCode());
- assertEquals("This error does not exist.", e.getMessage());
- assertEquals("NonexistentException", e.getType());
- }
- }
-
- @Test
- public void testInvlalidErrors()
- {
- log.info("testInvalidErrors");
-
-
- requestFactory.setBody(
- "{\n" +
- " \"error\":\n" +
- " {\n" +
- " \"message\": null,\n" +
- " \"type\": \"Whatever\",\n" +
- " \"code\": 999999999\n" +
- " }\n" +
- "}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(HttpClientErrorException e)
- {
- log.debug("{}", e.toString());
- }
- catch(Exception e)
- {
- fail("A wrong exception was thrown: " + e.toString());
- }
-
-
- requestFactory.setBody(
- "{\n" +
- " \"error\":\n" +
- " {\n" +
- " \"type\": \"Whatever\",\n" +
- " \"code\": 999999999\n" +
- " }\n" +
- "}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(HttpClientErrorException e)
- {
- log.debug("{}", e.toString());
- }
- catch(Exception e)
- {
- fail("A wrong exception was thrown: " + e.toString());
- }
-
-
- requestFactory.setBody(
- "{\n" +
- " \"error\":\n" +
- " {\n" +
- " \"message\": \"Not a Graph-Api-Exception.\",\n" +
- " \"type\": null,\n" +
- " \"code\": 999999999\n" +
- " }\n" +
- "}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(HttpClientErrorException e)
- {
- log.debug("{}", e.toString());
- }
- catch(Exception e)
- {
- fail("A wrong exception was thrown: " + e.toString());
- }
-
-
- requestFactory.setBody(
- "{\n" +
- " \"error\":\n" +
- " {\n" +
- " \"message\": \"Not a Graph-Api-Exception.\",\n" +
- " \"code\": 999999999\n" +
- " }\n" +
- "}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(HttpClientErrorException e)
- {
- log.debug("{}", e.toString());
- }
- catch(Exception e)
- {
- fail("A wrong exception was thrown: " + e.toString());
- }
-
-
- requestFactory.setBody(
- "{\n" +
- " \"error\":\n" +
- " {\n" +
- " \"message\": \"Not a Graph-Api-Exception.\",\n" +
- " \"type\": \"Whatever\",\n" +
- " \"code\": \"some string\"\n" +
- " }\n" +
- "}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(HttpClientErrorException e)
- {
- log.debug("{}", e.toString());
- }
- catch(Exception e)
- {
- fail("A wrong exception was thrown: " + e.toString());
- }
-
-
- requestFactory.setBody(
- "{\n" +
- " \"error\":\n" +
- " {\n" +
- " \"message\": \"Not a Graph-Api-Exception.\",\n" +
- " \"type\": \"Whatever\",\n" +
- " \"code\": 9.9\n" +
- " }\n" +
- "}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(HttpClientErrorException e)
- {
- log.debug("{}", e.toString());
- }
- catch(Exception e)
- {
- fail("A wrong exception was thrown: " + e.toString());
- }
-
-
- requestFactory.setBody(
- "{\n" +
- " \"error\":\n" +
- " {\n" +
- " \"message\": \"Not a Graph-Api-Exception.\",\n" +
- " \"type\": \"Whatever\",\n" +
- " \"code\": null\n" +
- " }\n" +
- "}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(HttpClientErrorException e)
- {
- log.debug("{}", e.toString());
- }
- catch(Exception e)
- {
- fail("A wrong exception was thrown: " + e.toString());
- }
-
-
- requestFactory.setBody(
- "{\n" +
- " \"error\":\n" +
- " {\n" +
- " \"message\": \"Not a Graph-Api-Exception.\",\n" +
- " \"type\": \"Whatever\"\n" +
- " }\n" +
- "}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(HttpClientErrorException e)
- {
- log.debug("{}", e.toString());
- }
- catch(Exception e)
- {
- fail("A wrong exception was thrown: " + e.toString());
- }
-
-
- requestFactory.setBody("{\"error\":{\"message\":null}}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(HttpClientErrorException e)
- {
- log.debug("{}", e.toString());
- }
- catch(Exception e)
- {
- fail("A wrong exception was thrown: " + e.toString());
- }
-
-
- requestFactory.setBody("{\"error\":{\"type\":null}}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(HttpClientErrorException e)
- {
- log.debug("{}", e.toString());
- }
- catch(Exception e)
- {
- fail("A wrong exception was thrown: " + e.toString());
- }
-
-
- requestFactory.setBody("{\"error\":{\"code\":null}}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(HttpClientErrorException e)
- {
- log.debug("{}", e.toString());
- }
- catch(Exception e)
- {
- fail("A wrong exception was thrown: " + e.toString());
- }
-
-
- requestFactory.setBody("{\"error\":{}}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(HttpClientErrorException e)
- {
- log.debug("{}", e.toString());
- }
- catch(Exception e)
- {
- fail("A wrong exception was thrown: " + e.toString());
- }
-
-
- requestFactory.setBody("{\"error\":\"some message\"}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(HttpClientErrorException e)
- {
- log.debug("{}", e.toString());
- }
- catch(Exception e)
- {
- fail("A wrong exception was thrown: " + e.toString());
- }
-
-
- requestFactory.setBody("{\"error\":null}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(HttpClientErrorException e)
- {
- log.debug("{}", e.toString());
- }
- catch(Exception e)
- {
- fail("A wrong exception was thrown: " + e.toString());
- }
-
-
- requestFactory.setBody("{\"some filed\":\"some message\"}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(HttpClientErrorException e)
- {
- log.debug("{}", e.toString());
- }
- catch(Exception e)
- {
- fail("A wrong exception was thrown: " + e.toString());
- }
-
-
- requestFactory.setBody("{}");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(HttpClientErrorException e)
- {
- log.debug("{}", e.toString());
- }
- catch(Exception e)
- {
- fail("A wrong exception was thrown: " + e.toString());
- }
-
-
- requestFactory.setBody("");
-
- try
- {
- clientTemplate.getForObject("ANY", SOME.class);
- fail("The expected exception was not thrown");
- }
- catch(HttpClientErrorException e)
- {
- log.debug("{}", e.toString());
- }
- catch(Exception e)
- {
- fail("A wrong exception was thrown: " + e.toString());
- }
- }
-
-
- @Before
- public void setUp()
- {
- requestFactory = new MockClientHttpRequestFactory();
- requestFactory.setStatus(HttpStatus.BAD_REQUEST);
- requestFactory.addHeader("Content-Type", "application/json");
- clientTemplate.setRequestFactory(requestFactory);
-
- clientTemplate.setErrorHandler(
- new GraphApiErrorHandler(
- (OAuth2ErrorHandler)clientTemplate.getErrorHandler()
- )
- );
-
- clientTemplate.setAccessTokenProvider(new AccessTokenProvider()
- {
- @Override
- public OAuth2AccessToken obtainAccessToken(
- OAuth2ProtectedResourceDetails details,
- AccessTokenRequest parameters
- )
- throws
- UserRedirectRequiredException,
- UserApprovalRequiredException,
- AccessDeniedException
- {
- return new OAuth2AccessToken() {
-
- @Override
- public Map<String, Object> getAdditionalInformation()
- {
- throw new UnsupportedOperationException("Not supported yet.");
- }
-
- @Override
- public Set<String> getScope()
- {
- throw new UnsupportedOperationException("Not supported yet.");
- }
-
- @Override
- public OAuth2RefreshToken getRefreshToken()
- {
- throw new UnsupportedOperationException("Not supported yet.");
- }
-
- @Override
- public String getTokenType()
- {
- return OAUTH2_TYPE;
- }
-
- @Override
- public boolean isExpired()
- {
- return false;
- }
-
- @Override
- public Date getExpiration()
- {
- throw new UnsupportedOperationException("Not supported yet.");
- }
-
- @Override
- public int getExpiresIn()
- {
- throw new UnsupportedOperationException("Not supported yet.");
- }
-
- @Override
- public String getValue()
- {
- return "ANY";
- }
- };
- }
-
- @Override
- public boolean supportsResource(OAuth2ProtectedResourceDetails resource)
- {
- return true;
- }
-
- @Override
- public OAuth2AccessToken refreshAccessToken(
- OAuth2ProtectedResourceDetails resource,
- OAuth2RefreshToken refreshToken,
- AccessTokenRequest request
- )
- throws
- UserRedirectRequiredException
- {
- throw new UnsupportedOperationException("Not supported yet.");
- }
-
- @Override
- public boolean supportsRefresh(OAuth2ProtectedResourceDetails resource)
- {
- return false;
- }
- });
- }
-
-
- static class SOME
- {
- }
-}
+++ /dev/null
-package de.juplo.facebook;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URI;
-import java.nio.charset.StandardCharsets;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.client.ClientHttpRequest;
-import org.springframework.http.client.ClientHttpRequestFactory;
-import org.springframework.http.client.ClientHttpResponse;
-
-
-
-/**
- *
- * @author kai
- */
-public class MockClientHttpRequestFactory implements ClientHttpRequestFactory
-{
- private static final Logger log =
- LoggerFactory.getLogger(MockClientHttpRequestFactory.class);
-
- private HttpStatus status = HttpStatus.OK;
- private HttpHeaders headers = new HttpHeaders();
- private String body = "";
-
-
- @Override
- public ClientHttpRequest createRequest(URI uri, HttpMethod method) throws IOException
- {
- return new MockClientHttpRequest(uri, method);
- }
-
- public void setStatus(HttpStatus status)
- {
- this.status = status;
- }
-
- public void setHeaders(HttpHeaders headers)
- {
- this.headers = headers;
- }
-
- public void addHeader(String name, String value)
- {
- headers.add(name, value);
- }
-
- public void setBody(String body)
- {
- log.trace(body);
- this.body = body;
- }
-
-
- class MockClientHttpRequest implements ClientHttpRequest
- {
- private final URI uri;
- private final HttpMethod method;
-
-
- public MockClientHttpRequest(URI uri, HttpMethod method)
- {
- this.uri = uri;
- this.method = method;
- }
-
-
- @Override
- public ClientHttpResponse execute() throws IOException
- {
- return new MockClientHttpResponse();
- }
-
- @Override
- public HttpMethod getMethod()
- {
- return method;
- }
-
- @Override
- public URI getURI()
- {
- return uri;
- }
-
- @Override
- public HttpHeaders getHeaders()
- {
- return headers;
- }
-
- @Override
- public OutputStream getBody() throws IOException
- {
- throw new UnsupportedOperationException("Not supported yet.");
- }
- }
-
-
- class MockClientHttpResponse implements ClientHttpResponse
- {
- @Override
- public HttpStatus getStatusCode() throws IOException
- {
- return status;
- }
-
- @Override
- public int getRawStatusCode() throws IOException
- {
- return status.value();
- }
-
- @Override
- public String getStatusText() throws IOException
- {
- return status.getReasonPhrase();
- }
-
- @Override
- public void close()
- {
- }
-
- @Override
- public InputStream getBody() throws IOException
- {
- return new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
- }
-
- @Override
- public HttpHeaders getHeaders()
- {
- return headers;
- }
- }
-}
--- /dev/null
+package de.juplo.facebook.client;
+
+import de.juplo.facebook.exceptions.UnsupportedGetRequestException;
+import de.juplo.facebook.exceptions.UnexpectedErrorException;
+import de.juplo.facebook.exceptions.RateExceededException;
+import de.juplo.facebook.exceptions.GraphApiException;
+import de.juplo.facebook.exceptions.UnknownErrorException;
+import de.juplo.facebook.exceptions.PageMigratedException;
+import java.util.Date;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Resource;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.oauth2.client.OAuth2RestTemplate;
+import org.springframework.security.oauth2.client.http.OAuth2ErrorHandler;
+import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
+import org.springframework.security.oauth2.client.resource.UserApprovalRequiredException;
+import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException;
+import org.springframework.security.oauth2.client.token.AccessTokenProvider;
+import org.springframework.security.oauth2.client.token.AccessTokenRequest;
+import org.springframework.security.oauth2.common.OAuth2AccessToken;
+import static org.springframework.security.oauth2.common.OAuth2AccessToken.OAUTH2_TYPE;
+import org.springframework.security.oauth2.common.OAuth2RefreshToken;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.web.client.HttpClientErrorException;
+
+
+
+/**
+ *
+ * @author kai
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(
+ locations = {
+ "classpath:/spring/test-facebook-error-handler.xml"
+ })
+public class GraphApiErrorHandlerTest
+{
+ private static final Logger log =
+ LoggerFactory.getLogger(GraphApiErrorHandlerTest.class);
+
+ @Resource
+ private OAuth2RestTemplate clientTemplate;
+
+ private MockClientHttpRequestFactory requestFactory;
+
+
+ @Test
+ public void testError1()
+ {
+ log.info("testError1");
+
+
+ requestFactory.setBody(
+ "{\n" +
+ " \"error\":\n" +
+ " {\n" +
+ " \"message\": \"An unknown error has occurred.\",\n" +
+ " \"type\": \"OAuthException\",\n" +
+ " \"code\": 1\n" +
+ " }\n" +
+ "}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(UnknownErrorException e)
+ {
+ log.debug("{}", e.toString());
+ assertEquals("invalid_request", e.getOAuth2ErrorCode());
+ assertEquals(1, e.getCode());
+ assertEquals("An unknown error has occurred.", e.getMessage());
+ assertEquals("OAuthException", e.getType());
+ }
+ }
+
+ @Test
+ public void testError2()
+ {
+ log.info("testError2");
+
+
+ requestFactory.setBody(
+ "{\n" +
+ " \"error\":\n" +
+ " {\n" +
+ " \"message\": \"An unexpected error has occurred. Please retry your request later.\",\n" +
+ " \"type\": \"OAuthException\",\n" +
+ " \"code\": 2\n" +
+ " }\n" +
+ "}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(UnexpectedErrorException e)
+ {
+ log.debug("{}", e.toString());
+ assertEquals("invalid_request", e.getOAuth2ErrorCode());
+ assertEquals(2, e.getCode());
+ assertEquals("An unexpected error has occurred. Please retry your request later.", e.getMessage());
+ assertEquals("OAuthException", e.getType());
+ }
+ }
+
+ @Test
+ public void testError21()
+ {
+ log.info("testError21");
+
+
+ requestFactory.setBody(
+ "{\n" +
+ " \"error\":\n" +
+ " {\n" +
+ " \"message\": \"(#21) Page ID 590408587650316 was migrated to page ID 1421620791415603. Please update your API calls to the new ID\",\n" +
+ " \"type\": \"OAuthException\",\n" +
+ " \"code\": 21\n" +
+ " }\n" +
+ "}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(PageMigratedException e)
+ {
+ log.debug("{}", e.toString());
+ assertEquals("invalid_request", e.getOAuth2ErrorCode());
+ assertEquals(21, e.getCode());
+ assertEquals("(#21) Page ID 590408587650316 was migrated to page ID 1421620791415603. Please update your API calls to the new ID", e.getMessage());
+ assertEquals("OAuthException", e.getType());
+ }
+ }
+
+ @Test
+ public void testError100()
+ {
+ log.info("testError100");
+
+
+ requestFactory.setBody(
+ "{\n" +
+ " \"error\":\n" +
+ " {\n" +
+ " \"message\": \"Unsupported get request.\",\n" +
+ " \"type\": \"GraphMethodException\",\n" +
+ " \"code\": 100\n" +
+ " }\n" +
+ "}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(UnsupportedGetRequestException e)
+ {
+ log.debug("{}", e.toString());
+ assertEquals("invalid_request", e.getOAuth2ErrorCode());
+ assertEquals(100, e.getCode());
+ assertEquals("Unsupported get request.", e.getMessage());
+ assertEquals("GraphMethodException", e.getType());
+ }
+ }
+
+ @Test
+ public void testError613()
+ {
+ log.info("testError613");
+
+
+ requestFactory.setBody(
+ "{\n" +
+ " \"error\":\n" +
+ " {\n" +
+ " \"message\": \"(#613) Calls to stream have exceeded the rate of 600 calls per 600 seconds.\",\n" +
+ " \"type\": \"OAuthException\",\n" +
+ " \"code\": 613\n" +
+ " }\n" +
+ "}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(RateExceededException e)
+ {
+ log.debug("{}", e.toString());
+ assertEquals("invalid_request", e.getOAuth2ErrorCode());
+ assertEquals(613, e.getCode());
+ assertEquals("(#613) Calls to stream have exceeded the rate of 600 calls per 600 seconds.", e.getMessage());
+ assertEquals("OAuthException", e.getType());
+ }
+ }
+
+ @Test
+ public void testUnmappedError()
+ {
+ log.info("testUnmappedError");
+
+
+ requestFactory.setBody(
+ "{\n" +
+ " \"error\":\n" +
+ " {\n" +
+ " \"message\": \"This error does not exist.\",\n" +
+ " \"type\": \"NonexistentException\",\n" +
+ " \"code\": 999999999\n" +
+ " }\n" +
+ "}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(GraphApiException e)
+ {
+ log.debug("{}", e.toString());
+ assertEquals("invalid_request", e.getOAuth2ErrorCode());
+ assertEquals(999999999, e.getCode());
+ assertEquals("This error does not exist.", e.getMessage());
+ assertEquals("NonexistentException", e.getType());
+ }
+ }
+
+ @Test
+ public void testInvlalidErrors()
+ {
+ log.info("testInvalidErrors");
+
+
+ requestFactory.setBody(
+ "{\n" +
+ " \"error\":\n" +
+ " {\n" +
+ " \"message\": null,\n" +
+ " \"type\": \"Whatever\",\n" +
+ " \"code\": 999999999\n" +
+ " }\n" +
+ "}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(HttpClientErrorException e)
+ {
+ log.debug("{}", e.toString());
+ }
+ catch(Exception e)
+ {
+ fail("A wrong exception was thrown: " + e.toString());
+ }
+
+
+ requestFactory.setBody(
+ "{\n" +
+ " \"error\":\n" +
+ " {\n" +
+ " \"type\": \"Whatever\",\n" +
+ " \"code\": 999999999\n" +
+ " }\n" +
+ "}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(HttpClientErrorException e)
+ {
+ log.debug("{}", e.toString());
+ }
+ catch(Exception e)
+ {
+ fail("A wrong exception was thrown: " + e.toString());
+ }
+
+
+ requestFactory.setBody(
+ "{\n" +
+ " \"error\":\n" +
+ " {\n" +
+ " \"message\": \"Not a Graph-Api-Exception.\",\n" +
+ " \"type\": null,\n" +
+ " \"code\": 999999999\n" +
+ " }\n" +
+ "}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(HttpClientErrorException e)
+ {
+ log.debug("{}", e.toString());
+ }
+ catch(Exception e)
+ {
+ fail("A wrong exception was thrown: " + e.toString());
+ }
+
+
+ requestFactory.setBody(
+ "{\n" +
+ " \"error\":\n" +
+ " {\n" +
+ " \"message\": \"Not a Graph-Api-Exception.\",\n" +
+ " \"code\": 999999999\n" +
+ " }\n" +
+ "}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(HttpClientErrorException e)
+ {
+ log.debug("{}", e.toString());
+ }
+ catch(Exception e)
+ {
+ fail("A wrong exception was thrown: " + e.toString());
+ }
+
+
+ requestFactory.setBody(
+ "{\n" +
+ " \"error\":\n" +
+ " {\n" +
+ " \"message\": \"Not a Graph-Api-Exception.\",\n" +
+ " \"type\": \"Whatever\",\n" +
+ " \"code\": \"some string\"\n" +
+ " }\n" +
+ "}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(HttpClientErrorException e)
+ {
+ log.debug("{}", e.toString());
+ }
+ catch(Exception e)
+ {
+ fail("A wrong exception was thrown: " + e.toString());
+ }
+
+
+ requestFactory.setBody(
+ "{\n" +
+ " \"error\":\n" +
+ " {\n" +
+ " \"message\": \"Not a Graph-Api-Exception.\",\n" +
+ " \"type\": \"Whatever\",\n" +
+ " \"code\": 9.9\n" +
+ " }\n" +
+ "}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(HttpClientErrorException e)
+ {
+ log.debug("{}", e.toString());
+ }
+ catch(Exception e)
+ {
+ fail("A wrong exception was thrown: " + e.toString());
+ }
+
+
+ requestFactory.setBody(
+ "{\n" +
+ " \"error\":\n" +
+ " {\n" +
+ " \"message\": \"Not a Graph-Api-Exception.\",\n" +
+ " \"type\": \"Whatever\",\n" +
+ " \"code\": null\n" +
+ " }\n" +
+ "}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(HttpClientErrorException e)
+ {
+ log.debug("{}", e.toString());
+ }
+ catch(Exception e)
+ {
+ fail("A wrong exception was thrown: " + e.toString());
+ }
+
+
+ requestFactory.setBody(
+ "{\n" +
+ " \"error\":\n" +
+ " {\n" +
+ " \"message\": \"Not a Graph-Api-Exception.\",\n" +
+ " \"type\": \"Whatever\"\n" +
+ " }\n" +
+ "}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(HttpClientErrorException e)
+ {
+ log.debug("{}", e.toString());
+ }
+ catch(Exception e)
+ {
+ fail("A wrong exception was thrown: " + e.toString());
+ }
+
+
+ requestFactory.setBody("{\"error\":{\"message\":null}}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(HttpClientErrorException e)
+ {
+ log.debug("{}", e.toString());
+ }
+ catch(Exception e)
+ {
+ fail("A wrong exception was thrown: " + e.toString());
+ }
+
+
+ requestFactory.setBody("{\"error\":{\"type\":null}}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(HttpClientErrorException e)
+ {
+ log.debug("{}", e.toString());
+ }
+ catch(Exception e)
+ {
+ fail("A wrong exception was thrown: " + e.toString());
+ }
+
+
+ requestFactory.setBody("{\"error\":{\"code\":null}}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(HttpClientErrorException e)
+ {
+ log.debug("{}", e.toString());
+ }
+ catch(Exception e)
+ {
+ fail("A wrong exception was thrown: " + e.toString());
+ }
+
+
+ requestFactory.setBody("{\"error\":{}}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(HttpClientErrorException e)
+ {
+ log.debug("{}", e.toString());
+ }
+ catch(Exception e)
+ {
+ fail("A wrong exception was thrown: " + e.toString());
+ }
+
+
+ requestFactory.setBody("{\"error\":\"some message\"}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(HttpClientErrorException e)
+ {
+ log.debug("{}", e.toString());
+ }
+ catch(Exception e)
+ {
+ fail("A wrong exception was thrown: " + e.toString());
+ }
+
+
+ requestFactory.setBody("{\"error\":null}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(HttpClientErrorException e)
+ {
+ log.debug("{}", e.toString());
+ }
+ catch(Exception e)
+ {
+ fail("A wrong exception was thrown: " + e.toString());
+ }
+
+
+ requestFactory.setBody("{\"some filed\":\"some message\"}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(HttpClientErrorException e)
+ {
+ log.debug("{}", e.toString());
+ }
+ catch(Exception e)
+ {
+ fail("A wrong exception was thrown: " + e.toString());
+ }
+
+
+ requestFactory.setBody("{}");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(HttpClientErrorException e)
+ {
+ log.debug("{}", e.toString());
+ }
+ catch(Exception e)
+ {
+ fail("A wrong exception was thrown: " + e.toString());
+ }
+
+
+ requestFactory.setBody("");
+
+ try
+ {
+ clientTemplate.getForObject("ANY", SOME.class);
+ fail("The expected exception was not thrown");
+ }
+ catch(HttpClientErrorException e)
+ {
+ log.debug("{}", e.toString());
+ }
+ catch(Exception e)
+ {
+ fail("A wrong exception was thrown: " + e.toString());
+ }
+ }
+
+
+ @Before
+ public void setUp()
+ {
+ requestFactory = new MockClientHttpRequestFactory();
+ requestFactory.setStatus(HttpStatus.BAD_REQUEST);
+ requestFactory.addHeader("Content-Type", "application/json");
+ clientTemplate.setRequestFactory(requestFactory);
+
+ clientTemplate.setErrorHandler(
+ new GraphApiErrorHandler(
+ (OAuth2ErrorHandler)clientTemplate.getErrorHandler()
+ )
+ );
+
+ clientTemplate.setAccessTokenProvider(new AccessTokenProvider()
+ {
+ @Override
+ public OAuth2AccessToken obtainAccessToken(
+ OAuth2ProtectedResourceDetails details,
+ AccessTokenRequest parameters
+ )
+ throws
+ UserRedirectRequiredException,
+ UserApprovalRequiredException,
+ AccessDeniedException
+ {
+ return new OAuth2AccessToken() {
+
+ @Override
+ public Map<String, Object> getAdditionalInformation()
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public Set<String> getScope()
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public OAuth2RefreshToken getRefreshToken()
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public String getTokenType()
+ {
+ return OAUTH2_TYPE;
+ }
+
+ @Override
+ public boolean isExpired()
+ {
+ return false;
+ }
+
+ @Override
+ public Date getExpiration()
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public int getExpiresIn()
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public String getValue()
+ {
+ return "ANY";
+ }
+ };
+ }
+
+ @Override
+ public boolean supportsResource(OAuth2ProtectedResourceDetails resource)
+ {
+ return true;
+ }
+
+ @Override
+ public OAuth2AccessToken refreshAccessToken(
+ OAuth2ProtectedResourceDetails resource,
+ OAuth2RefreshToken refreshToken,
+ AccessTokenRequest request
+ )
+ throws
+ UserRedirectRequiredException
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public boolean supportsRefresh(OAuth2ProtectedResourceDetails resource)
+ {
+ return false;
+ }
+ });
+ }
+
+
+ static class SOME
+ {
+ }
+}
--- /dev/null
+package de.juplo.facebook.client;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.client.ClientHttpRequest;
+import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.http.client.ClientHttpResponse;
+
+
+
+/**
+ *
+ * @author kai
+ */
+public class MockClientHttpRequestFactory implements ClientHttpRequestFactory
+{
+ private static final Logger log =
+ LoggerFactory.getLogger(MockClientHttpRequestFactory.class);
+
+ private HttpStatus status = HttpStatus.OK;
+ private HttpHeaders headers = new HttpHeaders();
+ private String body = "";
+
+
+ @Override
+ public ClientHttpRequest createRequest(URI uri, HttpMethod method) throws IOException
+ {
+ return new MockClientHttpRequest(uri, method);
+ }
+
+ public void setStatus(HttpStatus status)
+ {
+ this.status = status;
+ }
+
+ public void setHeaders(HttpHeaders headers)
+ {
+ this.headers = headers;
+ }
+
+ public void addHeader(String name, String value)
+ {
+ headers.add(name, value);
+ }
+
+ public void setBody(String body)
+ {
+ log.trace(body);
+ this.body = body;
+ }
+
+
+ class MockClientHttpRequest implements ClientHttpRequest
+ {
+ private final URI uri;
+ private final HttpMethod method;
+
+
+ public MockClientHttpRequest(URI uri, HttpMethod method)
+ {
+ this.uri = uri;
+ this.method = method;
+ }
+
+
+ @Override
+ public ClientHttpResponse execute() throws IOException
+ {
+ return new MockClientHttpResponse();
+ }
+
+ @Override
+ public HttpMethod getMethod()
+ {
+ return method;
+ }
+
+ @Override
+ public URI getURI()
+ {
+ return uri;
+ }
+
+ @Override
+ public HttpHeaders getHeaders()
+ {
+ return headers;
+ }
+
+ @Override
+ public OutputStream getBody() throws IOException
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }
+
+
+ class MockClientHttpResponse implements ClientHttpResponse
+ {
+ @Override
+ public HttpStatus getStatusCode() throws IOException
+ {
+ return status;
+ }
+
+ @Override
+ public int getRawStatusCode() throws IOException
+ {
+ return status.value();
+ }
+
+ @Override
+ public String getStatusText() throws IOException
+ {
+ return status.getReasonPhrase();
+ }
+
+ @Override
+ public void close()
+ {
+ }
+
+ @Override
+ public InputStream getBody() throws IOException
+ {
+ return new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public HttpHeaders getHeaders()
+ {
+ return headers;
+ }
+ }
+}