Refactoring: renamed package de.juplo.facebook.exceptions to errors
authorKai Moritz <kai@juplo.de>
Tue, 7 Jun 2016 13:10:08 +0000 (15:10 +0200)
committerKai Moritz <kai@juplo.de>
Tue, 7 Jun 2016 13:47:19 +0000 (15:47 +0200)
33 files changed:
src/main/java/de/juplo/facebook/FacebookUtils.java
src/main/java/de/juplo/facebook/errors/AccessTokenRequiredException.java [new file with mode: 0644]
src/main/java/de/juplo/facebook/errors/CallbackVerificationFailedException.java [new file with mode: 0644]
src/main/java/de/juplo/facebook/errors/FacebookErrorMessage.java [new file with mode: 0644]
src/main/java/de/juplo/facebook/errors/GraphApiErrorHandler.java [new file with mode: 0644]
src/main/java/de/juplo/facebook/errors/GraphApiException.java [new file with mode: 0644]
src/main/java/de/juplo/facebook/errors/GraphMethodException.java [new file with mode: 0644]
src/main/java/de/juplo/facebook/errors/OAuthException.java [new file with mode: 0644]
src/main/java/de/juplo/facebook/errors/PageMigratedException.java [new file with mode: 0644]
src/main/java/de/juplo/facebook/errors/RateExceededException.java [new file with mode: 0644]
src/main/java/de/juplo/facebook/errors/UnexpectedErrorException.java [new file with mode: 0644]
src/main/java/de/juplo/facebook/errors/UnknownErrorException.java [new file with mode: 0644]
src/main/java/de/juplo/facebook/errors/UnmappedErrorException.java [new file with mode: 0644]
src/main/java/de/juplo/facebook/errors/UnsupportedGetRequestException.java [new file with mode: 0644]
src/main/java/de/juplo/facebook/exceptions/AccessTokenRequiredException.java [deleted file]
src/main/java/de/juplo/facebook/exceptions/CallbackVerificationFailedException.java [deleted file]
src/main/java/de/juplo/facebook/exceptions/FacebookErrorMessage.java [deleted file]
src/main/java/de/juplo/facebook/exceptions/GraphApiErrorHandler.java [deleted file]
src/main/java/de/juplo/facebook/exceptions/GraphApiException.java [deleted file]
src/main/java/de/juplo/facebook/exceptions/GraphMethodException.java [deleted file]
src/main/java/de/juplo/facebook/exceptions/OAuthException.java [deleted file]
src/main/java/de/juplo/facebook/exceptions/PageMigratedException.java [deleted file]
src/main/java/de/juplo/facebook/exceptions/RateExceededException.java [deleted file]
src/main/java/de/juplo/facebook/exceptions/UnexpectedErrorException.java [deleted file]
src/main/java/de/juplo/facebook/exceptions/UnknownErrorException.java [deleted file]
src/main/java/de/juplo/facebook/exceptions/UnmappedErrorException.java [deleted file]
src/main/java/de/juplo/facebook/exceptions/UnsupportedGetRequestException.java [deleted file]
src/test/java/de/juplo/facebook/errors/FacebookErrorMessageMappingTest.java [new file with mode: 0644]
src/test/java/de/juplo/facebook/errors/GraphApiErrorHandlerTest.java [new file with mode: 0644]
src/test/java/de/juplo/facebook/errors/MockClientHttpRequestFactory.java [new file with mode: 0644]
src/test/java/de/juplo/facebook/exceptions/FacebookErrorMessageMappingTest.java [deleted file]
src/test/java/de/juplo/facebook/exceptions/GraphApiErrorHandlerTest.java [deleted file]
src/test/java/de/juplo/facebook/exceptions/MockClientHttpRequestFactory.java [deleted file]

index 1a76601..f6f6b37 100644 (file)
@@ -1,8 +1,8 @@
 package de.juplo.facebook;
 
 
+import de.juplo.facebook.errors.GraphApiErrorHandler;
 import de.juplo.facebook.token.SignedRequestAwareAuthorizationCodeAccessTokenProvider;
-import de.juplo.facebook.exceptions.GraphApiErrorHandler;
 import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
diff --git a/src/main/java/de/juplo/facebook/errors/AccessTokenRequiredException.java b/src/main/java/de/juplo/facebook/errors/AccessTokenRequiredException.java
new file mode 100644 (file)
index 0000000..5b54096
--- /dev/null
@@ -0,0 +1,15 @@
+package de.juplo.facebook.errors;
+
+
+
+/**
+ * 104: An access token is required to request this resource.
+ * @author Kai Moritz
+ */
+public class AccessTokenRequiredException extends OAuthException
+{
+  protected AccessTokenRequiredException(FacebookErrorMessage error)
+  {
+    super(error);
+  }
+}
diff --git a/src/main/java/de/juplo/facebook/errors/CallbackVerificationFailedException.java b/src/main/java/de/juplo/facebook/errors/CallbackVerificationFailedException.java
new file mode 100644 (file)
index 0000000..27ef936
--- /dev/null
@@ -0,0 +1,14 @@
+package de.juplo.facebook.errors;
+
+
+/**
+ * 2200: Callback verification failed.
+ * @author Kai Moritz
+ */
+public class CallbackVerificationFailedException extends OAuthException
+{
+  protected CallbackVerificationFailedException(FacebookErrorMessage error)
+  {
+    super(error);
+  }
+}
diff --git a/src/main/java/de/juplo/facebook/errors/FacebookErrorMessage.java b/src/main/java/de/juplo/facebook/errors/FacebookErrorMessage.java
new file mode 100644 (file)
index 0000000..495c665
--- /dev/null
@@ -0,0 +1,42 @@
+package de.juplo.facebook.errors;
+
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.annotation.JsonRootName;
+
+
+
+/**
+ * This class represents an error message from the Graph-API
+ *
+ * @see <a href="https://developers.facebook.com/docs/graph-api/using-graph-api/v2.5#errors">Graph-API Documentation</a>
+ * @author Kai Moritz
+ */
+@JsonRootName("error")
+@JsonPropertyOrder({
+  "message",
+  "type",
+  "code",
+  "error_subcode",
+  "error_user_title",
+  "error_user_msg",
+  "fbtrace_id"
+  })
+public class FacebookErrorMessage
+{
+  @JsonProperty("message")
+  String message;
+  @JsonProperty("type")
+  String type;
+  @JsonProperty("code")
+  Integer code;
+  @JsonProperty("error_subcode")
+  Integer subCode;
+  @JsonProperty("error_user_title")
+  String userTitle;
+  @JsonProperty("error_user_msg")
+  String userMessage;
+  @JsonProperty("fbtrace_id")
+  String traceId;
+}
diff --git a/src/main/java/de/juplo/facebook/errors/GraphApiErrorHandler.java b/src/main/java/de/juplo/facebook/errors/GraphApiErrorHandler.java
new file mode 100644 (file)
index 0000000..13a93e8
--- /dev/null
@@ -0,0 +1,140 @@
+package de.juplo.facebook.errors;
+
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.util.FileCopyUtils;
+import org.springframework.web.client.ResponseErrorHandler;
+
+
+
+/**
+ * Error-Handler for error-messages from the Facebook Graph-API.
+ * <p>
+ * This error-handler handels responses withe the HTTP-status code
+ * {@code 400 BAD REQUEST}. It tries to extract and parse the error-message
+ * from the HTTP-body. Successfully extracted and parsed messages are mapped
+ * to a hierarchy of exceptions, that reflects the hierarchy of the error-
+ * codes and -types.
+ * <p>
+ * If the HTTP-status-code of the response is not {@code 400 BAD REQUEST} or
+ * the HTTP-body could not be extracted or parsed, this error-handler
+ * delegates the handling to its parent.
+ *
+ * @see <a href="https://developers.facebook.com/docs/graph-api/using-graph-api/v2.5#errors">Graph-API Documentation</a>
+ * @see <a href="http://fbdevwiki.com/wiki/Error_codes">Inofficial Wiki For Facebook-Developers</a>
+ * @author Kai Moritz
+ */
+public class GraphApiErrorHandler implements ResponseErrorHandler
+{
+  private final static Logger LOG =
+      LoggerFactory.getLogger(GraphApiErrorHandler.class);
+
+  private final ResponseErrorHandler parent;
+
+
+  public GraphApiErrorHandler(ResponseErrorHandler errorHandler)
+  {
+    this.parent = errorHandler;
+  }
+
+
+  @Override
+  public boolean hasError(ClientHttpResponse response) throws IOException
+  {
+    return
+        HttpStatus.Series.CLIENT_ERROR.equals(response.getStatusCode().series())
+        || this.parent.hasError(response);
+  }
+
+  @Override
+  public void handleError(final ClientHttpResponse response) throws IOException
+  {
+    if (!HttpStatus.BAD_REQUEST.equals(response.getStatusCode()))
+    {
+      // We will only handle 400 BAD REQUEST
+      LOG.debug("ignoring response with status-code {}.", response.getStatusCode());
+      parent.handleError(response);
+      return;
+    }
+
+
+    if (response.getBody() == null)
+    {
+      // There is no body to interpret in the HTTP-message
+      LOG.warn("Could not convert the response into an exception, because there is no message-body.");
+      parent.handleError(response);
+      return;
+    }
+
+    final byte[] body = FileCopyUtils.copyToByteArray(response.getBody());
+    GraphApiException error;
+
+    try
+    {
+      error = GraphApiException.create(body);
+      if (LOG.isInfoEnabled())
+        LOG.info("error-response: {}", new String(body, Charset.forName("UTF-8")));
+    }
+    catch (Exception e)
+    {
+      // The body of the HTTP-message could not be parsed.
+      // Let the parent error-handler try to handle the response.
+
+      LOG.warn("Could not convert the response into an exception, because the body is unparsable: {}", body);
+
+      // To do so, we have to wrap the original response to fill in
+      // the buffered body, if needed
+      ClientHttpResponse buffered = new ClientHttpResponse()
+      {
+        @Override
+        public HttpStatus getStatusCode() throws IOException
+        {
+          return response.getStatusCode();
+        }
+
+        @Override
+        public synchronized InputStream getBody() throws IOException
+        {
+          return new ByteArrayInputStream(body);
+        }
+
+        @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();
+        }
+      };
+
+      parent.handleError(buffered);
+      return;
+    }
+
+    throw error;
+  }
+}
diff --git a/src/main/java/de/juplo/facebook/errors/GraphApiException.java b/src/main/java/de/juplo/facebook/errors/GraphApiException.java
new file mode 100644 (file)
index 0000000..45dd38a
--- /dev/null
@@ -0,0 +1,147 @@
+package de.juplo.facebook.errors;
+
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import java.io.IOException;
+import java.io.InputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
+
+
+
+/**
+ * Base exception for Facebook Graph-Api exceptions.
+ * 
+ * @author Kai Moritz
+ */
+public class GraphApiException extends OAuth2Exception
+{
+  public enum Type { OAuthException, GraphMethodException }
+
+
+  final static Logger LOG = LoggerFactory.getLogger(GraphApiException.class);
+  final static ObjectMapper OBJECT_MAPPER;
+
+  private final FacebookErrorMessage error;
+
+
+  static
+  {
+    OBJECT_MAPPER = new ObjectMapper();
+    OBJECT_MAPPER.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
+    OBJECT_MAPPER.configure(DeserializationFeature.ACCEPT_FLOAT_AS_INT, false);
+    OBJECT_MAPPER.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
+  }
+
+
+  public static GraphApiException create(InputStream in)
+      throws
+        IOException,
+        JsonParseException,
+        JsonMappingException
+  {
+    return create(OBJECT_MAPPER.readValue(in, FacebookErrorMessage.class));
+  }
+
+  public static GraphApiException create(byte[] message)
+      throws
+        IOException,
+        JsonParseException,
+        JsonMappingException
+  {
+    return create(OBJECT_MAPPER.readValue(message, FacebookErrorMessage.class));
+  }
+
+  public static GraphApiException create(FacebookErrorMessage error)
+  {
+    // see: http://fbdevwiki.com/wiki/Error_codes
+    switch(error.code)
+    {
+      // 1..99: general errors
+      case 1:     return new UnknownErrorException(error);
+      case 2:     return new UnexpectedErrorException(error);
+      case 21:    return new PageMigratedException(error);
+      // 100..199: graph method errors
+      case 100:   return new UnsupportedGetRequestException(error);
+      case 104:   return new AccessTokenRequiredException(error);
+      // 200..299: permission errors
+      // 300..399: data editing errors
+      // 400..449: authentication error
+      // 450..499: session errors
+      // 500..599: application messaging errors
+      // 600..699: FQL errors
+      case 613:   return new RateExceededException(error);
+      // 700..749: ref errors
+      // 750..799: application integration errors
+      // 900..949: application information errors
+      // 950..999: batch api errors
+      // 1000..1099: event api errors
+      // 1100..1199: live-message errors
+      case 2200:  return new CallbackVerificationFailedException(error);
+
+      default:
+        LOG.info("unmapped error: {}", error);
+        return new UnmappedErrorException(error);
+    }
+  }
+
+
+  protected GraphApiException(FacebookErrorMessage error)
+  {
+    super(error.message);
+    this.error = error;
+  }
+
+
+  public Type getType()
+  {
+    return error.type == null ? null : Type.valueOf(error.type);
+  }
+
+  public Integer getCode()
+  {
+    return error.code;
+  }
+
+  public Integer getSubCode()
+  {
+    return error.subCode;
+  }
+
+  public String getUserTitle()
+  {
+    return error.userTitle;
+  }
+
+  public String getUserMessage()
+  {
+    return error.userMessage;
+  }
+
+  public String getTraceId()
+  {
+    return error.traceId;
+  }
+
+
+  @Override
+  public String toString()
+  {
+    try
+    {
+      return OBJECT_MAPPER.writeValueAsString(error);
+    }
+    catch(JsonProcessingException e)
+    {
+      // This should never happen. But in case of a mistake: be verbose!
+      LOG.error("could not convert message into JSON: {}", e);
+      return e.getMessage();
+    }
+  }
+}
diff --git a/src/main/java/de/juplo/facebook/errors/GraphMethodException.java b/src/main/java/de/juplo/facebook/errors/GraphMethodException.java
new file mode 100644 (file)
index 0000000..afe83cb
--- /dev/null
@@ -0,0 +1,15 @@
+package de.juplo.facebook.errors;
+
+
+
+/**
+ * Baseclass for exceptions of type {@code GraphMethodException}.
+ * @author Kai Moritz
+ */
+public abstract class GraphMethodException extends GraphApiException
+{
+  GraphMethodException(FacebookErrorMessage error)
+  {
+    super(error);
+  }
+}
diff --git a/src/main/java/de/juplo/facebook/errors/OAuthException.java b/src/main/java/de/juplo/facebook/errors/OAuthException.java
new file mode 100644 (file)
index 0000000..4153dbc
--- /dev/null
@@ -0,0 +1,15 @@
+package de.juplo.facebook.errors;
+
+
+
+/**
+ * Baseclass for exceptions of type {@code OAuthException}.
+ * @author Kai Moritz
+ */
+public class OAuthException extends GraphApiException
+{
+  protected OAuthException(FacebookErrorMessage error)
+  {
+    super(error);
+  }
+}
diff --git a/src/main/java/de/juplo/facebook/errors/PageMigratedException.java b/src/main/java/de/juplo/facebook/errors/PageMigratedException.java
new file mode 100644 (file)
index 0000000..c339d83
--- /dev/null
@@ -0,0 +1,45 @@
+package de.juplo.facebook.errors;
+
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+
+/**
+ * 21: Page ID (XXX) was migrated to page ID (YYY).
+ * @author Kai Moritz
+ */
+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;
+
+
+  protected PageMigratedException(FacebookErrorMessage error)
+  {
+    super(error);
+    Matcher matcher = pattern.matcher(error.message);
+    if (!matcher.find())
+    {
+      String warning = "Could not parse migration-error: " + error.message;
+      LOG.error(warning);
+      throw new RuntimeException(warning);
+    }
+    oldId = Long.parseLong(matcher.group(1));
+    newId = Long.parseLong(matcher.group(2));
+  }
+
+
+  public Long getOldId()
+  {
+    return oldId;
+  }
+
+  public Long getNewId()
+  {
+    return newId;
+  }
+}
diff --git a/src/main/java/de/juplo/facebook/errors/RateExceededException.java b/src/main/java/de/juplo/facebook/errors/RateExceededException.java
new file mode 100644 (file)
index 0000000..e57cc45
--- /dev/null
@@ -0,0 +1,15 @@
+package de.juplo.facebook.errors;
+
+
+
+/**
+ * 613: Calls to stream have exceeded the rate of 600 calls per 600 seconds.
+ * @author Kai Moritz
+ */
+public class RateExceededException extends OAuthException
+{
+  protected RateExceededException(FacebookErrorMessage error)
+  {
+    super(error);
+  }
+}
diff --git a/src/main/java/de/juplo/facebook/errors/UnexpectedErrorException.java b/src/main/java/de/juplo/facebook/errors/UnexpectedErrorException.java
new file mode 100644 (file)
index 0000000..72c071b
--- /dev/null
@@ -0,0 +1,15 @@
+package de.juplo.facebook.errors;
+
+
+
+/**
+ * 2: An unexpected error has occurred.
+ * @author Kai Moritz
+ */
+public class UnexpectedErrorException extends OAuthException
+{
+  protected UnexpectedErrorException(FacebookErrorMessage error)
+  {
+    super(error);
+  }
+}
diff --git a/src/main/java/de/juplo/facebook/errors/UnknownErrorException.java b/src/main/java/de/juplo/facebook/errors/UnknownErrorException.java
new file mode 100644 (file)
index 0000000..7f5e8ff
--- /dev/null
@@ -0,0 +1,15 @@
+package de.juplo.facebook.errors;
+
+
+
+/**
+ * 1: An unknown error has occurred.
+ * @author Kai Moritz
+ */
+public class UnknownErrorException extends OAuthException
+{
+  protected UnknownErrorException(FacebookErrorMessage error)
+  {
+    super(error);
+  }
+}
diff --git a/src/main/java/de/juplo/facebook/errors/UnmappedErrorException.java b/src/main/java/de/juplo/facebook/errors/UnmappedErrorException.java
new file mode 100644 (file)
index 0000000..839de09
--- /dev/null
@@ -0,0 +1,18 @@
+package de.juplo.facebook.errors;
+
+
+
+/**
+ * Marker class for error-messages, that are not mapped yet.
+ * Pleas help us to complete the list of possible errors and report errors,
+ * that are mapped to this class to info@juplo.de. Thanxs!
+ *
+ * @author Kai Moritz
+ */
+public class UnmappedErrorException extends GraphApiException
+{
+  protected  UnmappedErrorException(FacebookErrorMessage error)
+  {
+    super(error);
+  }
+}
diff --git a/src/main/java/de/juplo/facebook/errors/UnsupportedGetRequestException.java b/src/main/java/de/juplo/facebook/errors/UnsupportedGetRequestException.java
new file mode 100644 (file)
index 0000000..4a5a9b8
--- /dev/null
@@ -0,0 +1,15 @@
+package de.juplo.facebook.errors;
+
+
+
+/**
+ * 100: Unsupported get request.
+ * @author Kai Moritz
+ */
+public class UnsupportedGetRequestException extends GraphMethodException
+{
+  protected UnsupportedGetRequestException(FacebookErrorMessage error)
+  {
+    super(error);
+  }
+}
diff --git a/src/main/java/de/juplo/facebook/exceptions/AccessTokenRequiredException.java b/src/main/java/de/juplo/facebook/exceptions/AccessTokenRequiredException.java
deleted file mode 100644 (file)
index c55fc36..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package de.juplo.facebook.exceptions;
-
-
-
-/**
- * 104: An access token is required to request this resource.
- * @author Kai Moritz
- */
-public class AccessTokenRequiredException extends OAuthException
-{
-  protected AccessTokenRequiredException(FacebookErrorMessage error)
-  {
-    super(error);
-  }
-}
diff --git a/src/main/java/de/juplo/facebook/exceptions/CallbackVerificationFailedException.java b/src/main/java/de/juplo/facebook/exceptions/CallbackVerificationFailedException.java
deleted file mode 100644 (file)
index 5951471..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-package de.juplo.facebook.exceptions;
-
-
-/**
- * 2200: Callback verification failed.
- * @author Kai Moritz
- */
-public class CallbackVerificationFailedException extends OAuthException
-{
-  protected CallbackVerificationFailedException(FacebookErrorMessage error)
-  {
-    super(error);
-  }
-}
diff --git a/src/main/java/de/juplo/facebook/exceptions/FacebookErrorMessage.java b/src/main/java/de/juplo/facebook/exceptions/FacebookErrorMessage.java
deleted file mode 100644 (file)
index a1bd4b3..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-package de.juplo.facebook.exceptions;
-
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.JsonPropertyOrder;
-import com.fasterxml.jackson.annotation.JsonRootName;
-
-
-
-/**
- * This class represents an error message from the Graph-API
- *
- * @see <a href="https://developers.facebook.com/docs/graph-api/using-graph-api/v2.5#errors">Graph-API Documentation</a>
- * @author Kai Moritz
- */
-@JsonRootName("error")
-@JsonPropertyOrder({
-  "message",
-  "type",
-  "code",
-  "error_subcode",
-  "error_user_title",
-  "error_user_msg",
-  "fbtrace_id"
-  })
-public class FacebookErrorMessage
-{
-  @JsonProperty("message")
-  String message;
-  @JsonProperty("type")
-  String type;
-  @JsonProperty("code")
-  Integer code;
-  @JsonProperty("error_subcode")
-  Integer subCode;
-  @JsonProperty("error_user_title")
-  String userTitle;
-  @JsonProperty("error_user_msg")
-  String userMessage;
-  @JsonProperty("fbtrace_id")
-  String traceId;
-}
diff --git a/src/main/java/de/juplo/facebook/exceptions/GraphApiErrorHandler.java b/src/main/java/de/juplo/facebook/exceptions/GraphApiErrorHandler.java
deleted file mode 100644 (file)
index 974bf85..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-package de.juplo.facebook.exceptions;
-
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.Charset;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.client.ClientHttpResponse;
-import org.springframework.util.FileCopyUtils;
-import org.springframework.web.client.ResponseErrorHandler;
-
-
-
-/**
- * Error-Handler for error-messages from the Facebook Graph-API.
- * <p>
- * This error-handler handels responses withe the HTTP-status code
- * {@code 400 BAD REQUEST}. It tries to extract and parse the error-message
- * from the HTTP-body. Successfully extracted and parsed messages are mapped
- * to a hierarchy of exceptions, that reflects the hierarchy of the error-
- * codes and -types.
- * <p>
- * If the HTTP-status-code of the response is not {@code 400 BAD REQUEST} or
- * the HTTP-body could not be extracted or parsed, this error-handler
- * delegates the handling to its parent.
- *
- * @see <a href="https://developers.facebook.com/docs/graph-api/using-graph-api/v2.5#errors">Graph-API Documentation</a>
- * @see <a href="http://fbdevwiki.com/wiki/Error_codes">Inofficial Wiki For Facebook-Developers</a>
- * @author Kai Moritz
- */
-public class GraphApiErrorHandler implements ResponseErrorHandler
-{
-  private final static Logger LOG =
-      LoggerFactory.getLogger(GraphApiErrorHandler.class);
-
-  private final ResponseErrorHandler parent;
-
-
-  public GraphApiErrorHandler(ResponseErrorHandler errorHandler)
-  {
-    this.parent = errorHandler;
-  }
-
-
-  @Override
-  public boolean hasError(ClientHttpResponse response) throws IOException
-  {
-    return
-        HttpStatus.Series.CLIENT_ERROR.equals(response.getStatusCode().series())
-        || this.parent.hasError(response);
-  }
-
-  @Override
-  public void handleError(final ClientHttpResponse response) throws IOException
-  {
-    if (!HttpStatus.BAD_REQUEST.equals(response.getStatusCode()))
-    {
-      // We will only handle 400 BAD REQUEST
-      LOG.debug("ignoring response with status-code {}.", response.getStatusCode());
-      parent.handleError(response);
-      return;
-    }
-
-
-    if (response.getBody() == null)
-    {
-      // There is no body to interpret in the HTTP-message
-      LOG.warn("Could not convert the response into an exception, because there is no message-body.");
-      parent.handleError(response);
-      return;
-    }
-
-    final byte[] body = FileCopyUtils.copyToByteArray(response.getBody());
-    GraphApiException error;
-
-    try
-    {
-      error = GraphApiException.create(body);
-      if (LOG.isInfoEnabled())
-        LOG.info("error-response: {}", new String(body, Charset.forName("UTF-8")));
-    }
-    catch (Exception e)
-    {
-      // The body of the HTTP-message could not be parsed.
-      // Let the parent error-handler try to handle the response.
-
-      LOG.warn("Could not convert the response into an exception, because the body is unparsable: {}", body);
-
-      // To do so, we have to wrap the original response to fill in
-      // the buffered body, if needed
-      ClientHttpResponse buffered = new ClientHttpResponse()
-      {
-        @Override
-        public HttpStatus getStatusCode() throws IOException
-        {
-          return response.getStatusCode();
-        }
-
-        @Override
-        public synchronized InputStream getBody() throws IOException
-        {
-          return new ByteArrayInputStream(body);
-        }
-
-        @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();
-        }
-      };
-
-      parent.handleError(buffered);
-      return;
-    }
-
-    throw error;
-  }
-}
diff --git a/src/main/java/de/juplo/facebook/exceptions/GraphApiException.java b/src/main/java/de/juplo/facebook/exceptions/GraphApiException.java
deleted file mode 100644 (file)
index 375c5b8..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-package de.juplo.facebook.exceptions;
-
-import com.fasterxml.jackson.core.JsonParseException;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.JsonMappingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SerializationFeature;
-import java.io.IOException;
-import java.io.InputStream;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
-
-/**
- * Base exception for Facebook Graph-Api exceptions.
- * 
- * @author Kai Moritz
- */
-public class GraphApiException extends OAuth2Exception
-{
-  public enum Type { OAuthException, GraphMethodException }
-
-
-  final static Logger LOG = LoggerFactory.getLogger(GraphApiException.class);
-  final static ObjectMapper OBJECT_MAPPER;
-
-  private final FacebookErrorMessage error;
-
-
-  static
-  {
-    OBJECT_MAPPER = new ObjectMapper();
-    OBJECT_MAPPER.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
-    OBJECT_MAPPER.configure(DeserializationFeature.ACCEPT_FLOAT_AS_INT, false);
-    OBJECT_MAPPER.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
-  }
-
-
-  public static GraphApiException create(InputStream in)
-      throws
-        IOException,
-        JsonParseException,
-        JsonMappingException
-  {
-    return create(OBJECT_MAPPER.readValue(in, FacebookErrorMessage.class));
-  }
-
-  public static GraphApiException create(byte[] message)
-      throws
-        IOException,
-        JsonParseException,
-        JsonMappingException
-  {
-    return create(OBJECT_MAPPER.readValue(message, FacebookErrorMessage.class));
-  }
-
-  public static GraphApiException create(FacebookErrorMessage error)
-  {
-    // see: http://fbdevwiki.com/wiki/Error_codes
-    switch(error.code)
-    {
-      // 1..99: general errors
-      case 1:     return new UnknownErrorException(error);
-      case 2:     return new UnexpectedErrorException(error);
-      case 21:    return new PageMigratedException(error);
-      // 100..199: graph method errors
-      case 100:   return new UnsupportedGetRequestException(error);
-      case 104:   return new AccessTokenRequiredException(error);
-      // 200..299: permission errors
-      // 300..399: data editing errors
-      // 400..449: authentication error
-      // 450..499: session errors
-      // 500..599: application messaging errors
-      // 600..699: FQL errors
-      case 613:   return new RateExceededException(error);
-      // 700..749: ref errors
-      // 750..799: application integration errors
-      // 900..949: application information errors
-      // 950..999: batch api errors
-      // 1000..1099: event api errors
-      // 1100..1199: live-message errors
-      case 2200:  return new CallbackVerificationFailedException(error);
-
-      default:
-        LOG.info("unmapped error: {}", error);
-        return new UnmappedErrorException(error);
-    }
-  }
-
-
-  protected GraphApiException(FacebookErrorMessage error)
-  {
-    super(error.message);
-    this.error = error;
-  }
-
-
-  public Type getType()
-  {
-    return error.type == null ? null : Type.valueOf(error.type);
-  }
-
-  public Integer getCode()
-  {
-    return error.code;
-  }
-
-  public Integer getSubCode()
-  {
-    return error.subCode;
-  }
-
-  public String getUserTitle()
-  {
-    return error.userTitle;
-  }
-
-  public String getUserMessage()
-  {
-    return error.userMessage;
-  }
-
-  public String getTraceId()
-  {
-    return error.traceId;
-  }
-
-
-  @Override
-  public String toString()
-  {
-    try
-    {
-      return OBJECT_MAPPER.writeValueAsString(error);
-    }
-    catch(JsonProcessingException e)
-    {
-      // This should never happen. But in case of a mistake: be verbose!
-      LOG.error("could not convert message into JSON: {}", e);
-      return e.getMessage();
-    }
-  }
-}
diff --git a/src/main/java/de/juplo/facebook/exceptions/GraphMethodException.java b/src/main/java/de/juplo/facebook/exceptions/GraphMethodException.java
deleted file mode 100644 (file)
index 7a8afc5..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-package de.juplo.facebook.exceptions;
-
-
-/**
- * Baseclass for exceptions of type {@code GraphMethodException}.
- * @author Kai Moritz
- */
-public abstract class GraphMethodException extends GraphApiException
-{
-  GraphMethodException(FacebookErrorMessage error)
-  {
-    super(error);
-  }
-}
diff --git a/src/main/java/de/juplo/facebook/exceptions/OAuthException.java b/src/main/java/de/juplo/facebook/exceptions/OAuthException.java
deleted file mode 100644 (file)
index 2235dde..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package de.juplo.facebook.exceptions;
-
-
-
-/**
- * Baseclass for exceptions of type {@code OAuthException}.
- * @author Kai Moritz
- */
-public class OAuthException extends GraphApiException
-{
-  protected OAuthException(FacebookErrorMessage error)
-  {
-    super(error);
-  }
-}
diff --git a/src/main/java/de/juplo/facebook/exceptions/PageMigratedException.java b/src/main/java/de/juplo/facebook/exceptions/PageMigratedException.java
deleted file mode 100644 (file)
index 88917dc..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-package de.juplo.facebook.exceptions;
-
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-
-
-/**
- * 21: Page ID (XXX) was migrated to page ID (YYY).
- * @author Kai Moritz
- */
-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;
-
-
-  protected PageMigratedException(FacebookErrorMessage error)
-  {
-    super(error);
-    Matcher matcher = pattern.matcher(error.message);
-    if (!matcher.find())
-    {
-      String warning = "Could not parse migration-error: " + error.message;
-      LOG.error(warning);
-      throw new RuntimeException(warning);
-    }
-    oldId = Long.parseLong(matcher.group(1));
-    newId = Long.parseLong(matcher.group(2));
-  }
-
-
-  public Long getOldId()
-  {
-    return oldId;
-  }
-
-  public Long getNewId()
-  {
-    return newId;
-  }
-}
diff --git a/src/main/java/de/juplo/facebook/exceptions/RateExceededException.java b/src/main/java/de/juplo/facebook/exceptions/RateExceededException.java
deleted file mode 100644 (file)
index adc0b4a..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-package de.juplo.facebook.exceptions;
-
-
-/**
- * 613: Calls to stream have exceeded the rate of 600 calls per 600 seconds.
- * @author Kai Moritz
- */
-public class RateExceededException extends OAuthException
-{
-  protected RateExceededException(FacebookErrorMessage error)
-  {
-    super(error);
-  }
-}
diff --git a/src/main/java/de/juplo/facebook/exceptions/UnexpectedErrorException.java b/src/main/java/de/juplo/facebook/exceptions/UnexpectedErrorException.java
deleted file mode 100644 (file)
index c639c26..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package de.juplo.facebook.exceptions;
-
-
-
-/**
- * 2: An unexpected error has occurred.
- * @author Kai Moritz
- */
-public class UnexpectedErrorException extends OAuthException
-{
-  protected UnexpectedErrorException(FacebookErrorMessage error)
-  {
-    super(error);
-  }
-}
diff --git a/src/main/java/de/juplo/facebook/exceptions/UnknownErrorException.java b/src/main/java/de/juplo/facebook/exceptions/UnknownErrorException.java
deleted file mode 100644 (file)
index b38341a..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package de.juplo.facebook.exceptions;
-
-
-
-/**
- * 1: An unknown error has occurred.
- * @author Kai Moritz
- */
-public class UnknownErrorException extends OAuthException
-{
-  protected UnknownErrorException(FacebookErrorMessage error)
-  {
-    super(error);
-  }
-}
diff --git a/src/main/java/de/juplo/facebook/exceptions/UnmappedErrorException.java b/src/main/java/de/juplo/facebook/exceptions/UnmappedErrorException.java
deleted file mode 100644 (file)
index 2ed9b95..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-package de.juplo.facebook.exceptions;
-
-
-/**
- * Marker class for error-messages, that are not mapped yet.
- * Pleas help us to complete the list of possible errors and report errors,
- * that are mapped to this class to info@juplo.de. Thanxs!
- *
- * @author Kai Moritz
- */
-public class UnmappedErrorException extends GraphApiException
-{
-  protected  UnmappedErrorException(FacebookErrorMessage error)
-  {
-    super(error);
-  }
-}
diff --git a/src/main/java/de/juplo/facebook/exceptions/UnsupportedGetRequestException.java b/src/main/java/de/juplo/facebook/exceptions/UnsupportedGetRequestException.java
deleted file mode 100644 (file)
index 6905808..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-package de.juplo.facebook.exceptions;
-
-
-/**
- * 100: Unsupported get request.
- * @author Kai Moritz
- */
-public class UnsupportedGetRequestException extends GraphMethodException
-{
-  protected UnsupportedGetRequestException(FacebookErrorMessage error)
-  {
-    super(error);
-  }
-}
diff --git a/src/test/java/de/juplo/facebook/errors/FacebookErrorMessageMappingTest.java b/src/test/java/de/juplo/facebook/errors/FacebookErrorMessageMappingTest.java
new file mode 100644 (file)
index 0000000..08fb372
--- /dev/null
@@ -0,0 +1,61 @@
+package de.juplo.facebook.errors;
+
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import static de.juplo.facebook.errors.GraphApiException.OBJECT_MAPPER;
+import de.juplo.facebook.errors.GraphApiException.Type;
+import java.io.IOException;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+
+
+/**
+ * @author Kai Moritz
+ */
+public class FacebookErrorMessageMappingTest
+{
+  final String example =
+      "{" +
+          "\"error\":{" +
+              "\"message\":\"Message describing the error\"," +
+              "\"type\":\"OAuthException\"," +
+              "\"code\":190," +
+              "\"error_subcode\":460," +
+              "\"error_user_title\":\"A title\"," +
+              "\"error_user_msg\":\"A message\"," +
+              "\"fbtrace_id\":\"EJplcsCHuLu\"" +
+          "}" +
+      "}";
+
+
+  @Test
+  public void testSerialize() throws JsonProcessingException
+  {
+    FacebookErrorMessage error = new FacebookErrorMessage();
+    error.message = "Message describing the error";
+    error.type = Type.OAuthException.name();
+    error.code = 190;
+    error.subCode = 460;
+    error.userTitle = "A title";
+    error.userMessage = "A message";
+    error.traceId = "EJplcsCHuLu";
+
+    assertEquals(example, OBJECT_MAPPER.writeValueAsString(error));
+  }
+
+  @Test
+  public void testDeserialize() throws IOException
+  {
+    FacebookErrorMessage error =
+        OBJECT_MAPPER.readValue(example, FacebookErrorMessage.class);
+
+    assertEquals("Message describing the error", error.message);
+    assertEquals(Type.OAuthException.name(), error.type);
+    assertEquals(new Integer(190), error.code);
+    assertEquals(new Integer(460), error.subCode);
+    assertEquals("A title", error.userTitle);
+    assertEquals("A message", error.userMessage);
+    assertEquals("EJplcsCHuLu", error.traceId);
+  }
+}
diff --git a/src/test/java/de/juplo/facebook/errors/GraphApiErrorHandlerTest.java b/src/test/java/de/juplo/facebook/errors/GraphApiErrorHandlerTest.java
new file mode 100644 (file)
index 0000000..9fc7fc3
--- /dev/null
@@ -0,0 +1,802 @@
+package de.juplo.facebook.errors;
+
+
+import de.juplo.facebook.errors.GraphApiException.Type;
+import java.util.Date;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Resource;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+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 Moritz
+ */
+@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(new Integer(1), e.getCode());
+      assertEquals("An unknown error has occurred.", e.getMessage());
+      assertEquals(Type.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(new Integer(2), e.getCode());
+      assertEquals("An unexpected error has occurred. Please retry your request later.", e.getMessage());
+      assertEquals(Type.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(new Integer(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(Type.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(new Integer(100), e.getCode());
+      assertEquals("Unsupported get request.", e.getMessage());
+      assertEquals(Type.GraphMethodException, e.getType());
+    }
+  }
+
+  @Test
+  public void testError104()
+  {
+    log.info("testError104");
+
+    requestFactory.setBody("{\"error\":{\"message\":\"An access token is required to request this resource.\",\"type\":\"OAuthException\",\"code\":104,\"fbtrace_id\":\"E2Jjkj5++LL\"}}");
+
+    try
+    {
+      clientTemplate.getForObject("ANY", SOME.class);
+      fail("The expected exception was not thrown");
+    }
+    catch(AccessTokenRequiredException e)
+    {
+      log.debug("{}", e.toString());
+      assertEquals("invalid_request", e.getOAuth2ErrorCode());
+      assertEquals(new Integer(104), e.getCode());
+      assertEquals("An access token is required to request this resource.", e.getMessage());
+      assertEquals(Type.OAuthException, e.getType());
+      assertEquals("E2Jjkj5++LL", e.getTraceId());
+    }
+  }
+
+  @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(new Integer(613), e.getCode());
+      assertEquals("(#613) Calls to stream have exceeded the rate of 600 calls per 600 seconds.", e.getMessage());
+      assertEquals(Type.OAuthException, e.getType());
+    }
+  }
+
+  @Test
+  public void testError2200()
+  {
+    requestFactory.setBody("{\"error\":{\"message\":\"(#2200) callback verification failed: \",\"type\":\"OAuthException\",\"code\":2200,\"fbtrace_id\":\"ESLjoZKvPXg\"}}");
+    
+    try
+    {
+      clientTemplate.getForObject("ANY", SOME.class);
+      fail("The expected exception was not thrown");
+    }
+    catch(CallbackVerificationFailedException e)
+    {
+      log.debug("{}", e.toString());
+      assertEquals("invalid_request", e.getOAuth2ErrorCode());
+      assertEquals(new Integer(2200), e.getCode());
+      assertEquals("(#2200) callback verification failed: ", e.getMessage());
+      assertEquals(Type.OAuthException, e.getType());
+      assertEquals("ESLjoZKvPXg", e.getTraceId());
+    }
+  }
+
+  @Test
+  public void testUnmappedError()
+  {
+    log.info("testUnmappedError");
+
+
+    requestFactory.setBody(
+        "{\n" +
+        "  \"error\":\n" +
+        "  {\n" +
+        "    \"message\": \"This error does not exist.\",\n" +
+        "    \"type\": \"NonexistentTypeException\",\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(new Integer(999999999), e.getCode());
+      assertEquals("This error does not exist.", e.getMessage());
+      try
+      {
+        Type type = e.getType();
+        log.error("unknown type: {}", type);
+        fail("unmapped type was resolved by enum: " + type);
+      }
+      catch (IllegalArgumentException ee) {}
+    }
+  }
+
+  @Test
+  public void testUnmappedErrors()
+  {
+    log.info("testUnmappedErrors");
+
+
+    requestFactory.setBody(
+        "{\n" +
+        "  \"error\":\n" +
+        "  {\n" +
+        "    \"message\": null,\n" +
+        "    \"type\": \"WhateverTypeException\",\n" +
+        "    \"code\": 999999999\n" +
+        "  }\n" +
+        "}");
+
+    try
+    {
+      clientTemplate.getForObject("ANY", SOME.class);
+      fail("The expected exception was not thrown");
+    }
+    catch(UnmappedErrorException e)
+    {
+      log.debug("{}", e.toString());
+      assertNull(e.getMessage());
+      try
+      {
+        Type type = e.getType();
+        log.error("unknown type: {}", type);
+        fail("unmapped type was resolved by enum: " + type);
+      }
+      catch (IllegalArgumentException ee) {}
+      assertEquals(new Integer(999999999), e.getCode());
+      assertNull(e.getSubCode());
+      assertNull(e.getUserTitle());
+      assertNull(e.getUserMessage());
+      assertNull(e.getTraceId());
+    }
+    catch(Exception e)
+    {
+      fail("A wrong exception was thrown: " + e.toString());
+    }
+
+
+    requestFactory.setBody(
+        "{\n" +
+        "  \"error\":\n" +
+        "  {\n" +
+        "    \"type\": \"WhateverTypeException\",\n" +
+        "    \"code\": 999999999\n" +
+        "  }\n" +
+        "}");
+
+    try
+    {
+      clientTemplate.getForObject("ANY", SOME.class);
+      fail("The expected exception was not thrown");
+    }
+    catch(UnmappedErrorException e)
+    {
+      log.debug("{}", e.toString());
+      assertNull(e.getMessage());
+      try
+      {
+        Type type = e.getType();
+        log.error("unknown type: {}", type);
+        fail("unmapped type was resolved by enum: " + type);
+      }
+      catch (IllegalArgumentException ee) {}
+      assertEquals(new Integer(999999999), e.getCode());
+      assertNull(e.getSubCode());
+      assertNull(e.getUserTitle());
+      assertNull(e.getUserMessage());
+      assertNull(e.getTraceId());
+    }
+    catch(Exception e)
+    {
+      fail("A wrong exception was thrown: " + e.toString());
+    }
+
+
+    requestFactory.setBody(
+        "{\n" +
+        "  \"error\":\n" +
+        "  {\n" +
+        "    \"message\": \"An unmapped 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(UnmappedErrorException e)
+    {
+      log.debug("{}", e.toString());
+      assertEquals("An unmapped Graph-API-Exception.", e.getMessage());
+      assertNull(e.getType());
+      assertEquals(new Integer(999999999), e.getCode());
+      assertNull(e.getSubCode());
+      assertNull(e.getUserTitle());
+      assertNull(e.getUserMessage());
+      assertNull(e.getTraceId());
+    }
+    catch(Exception e)
+    {
+      fail("A wrong exception was thrown: " + e.toString());
+    }
+
+
+    requestFactory.setBody(
+        "{\n" +
+        "  \"error\":\n" +
+        "  {\n" +
+        "    \"message\": \"An unmapped Graph-API-Exception.\",\n" +
+        "    \"code\": 999999999\n" +
+        "  }\n" +
+        "}");
+
+    try
+    {
+      clientTemplate.getForObject("ANY", SOME.class);
+      fail("The expected exception was not thrown");
+    }
+    catch(UnmappedErrorException e)
+    {
+      log.debug("{}", e.toString());
+      assertEquals("An unmapped Graph-API-Exception.", e.getMessage());
+      assertNull(e.getType());
+      assertEquals(new Integer(999999999), e.getCode());
+      assertNull(e.getSubCode());
+      assertNull(e.getUserTitle());
+      assertNull(e.getUserMessage());
+      assertNull(e.getTraceId());
+    }
+    catch(Exception e)
+    {
+      fail("A wrong exception was thrown: " + e.toString());
+    }
+  }
+
+  @Test
+  public void testInvlalidErrors()
+  {
+    log.info("testInvalidErrors");
+
+
+    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
+  {
+  }
+}
diff --git a/src/test/java/de/juplo/facebook/errors/MockClientHttpRequestFactory.java b/src/test/java/de/juplo/facebook/errors/MockClientHttpRequestFactory.java
new file mode 100644 (file)
index 0000000..d5b24c3
--- /dev/null
@@ -0,0 +1,145 @@
+package de.juplo.facebook.errors;
+
+
+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;
+    }
+  }
+}
diff --git a/src/test/java/de/juplo/facebook/exceptions/FacebookErrorMessageMappingTest.java b/src/test/java/de/juplo/facebook/exceptions/FacebookErrorMessageMappingTest.java
deleted file mode 100644 (file)
index ed1cba8..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-package de.juplo.facebook.exceptions;
-
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import static de.juplo.facebook.exceptions.GraphApiException.OBJECT_MAPPER;
-import de.juplo.facebook.exceptions.GraphApiException.Type;
-import java.io.IOException;
-import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-
-
-
-/**
- * @author Kai Moritz
- */
-public class FacebookErrorMessageMappingTest
-{
-  final String example =
-      "{" +
-          "\"error\":{" +
-              "\"message\":\"Message describing the error\"," +
-              "\"type\":\"OAuthException\"," +
-              "\"code\":190," +
-              "\"error_subcode\":460," +
-              "\"error_user_title\":\"A title\"," +
-              "\"error_user_msg\":\"A message\"," +
-              "\"fbtrace_id\":\"EJplcsCHuLu\"" +
-          "}" +
-      "}";
-
-
-  @Test
-  public void testSerialize() throws JsonProcessingException
-  {
-    FacebookErrorMessage error = new FacebookErrorMessage();
-    error.message = "Message describing the error";
-    error.type = Type.OAuthException.name();
-    error.code = 190;
-    error.subCode = 460;
-    error.userTitle = "A title";
-    error.userMessage = "A message";
-    error.traceId = "EJplcsCHuLu";
-
-    assertEquals(example, OBJECT_MAPPER.writeValueAsString(error));
-  }
-
-  @Test
-  public void testDeserialize() throws IOException
-  {
-    FacebookErrorMessage error =
-        OBJECT_MAPPER.readValue(example, FacebookErrorMessage.class);
-
-    assertEquals("Message describing the error", error.message);
-    assertEquals(Type.OAuthException.name(), error.type);
-    assertEquals(new Integer(190), error.code);
-    assertEquals(new Integer(460), error.subCode);
-    assertEquals("A title", error.userTitle);
-    assertEquals("A message", error.userMessage);
-    assertEquals("EJplcsCHuLu", error.traceId);
-  }
-}
diff --git a/src/test/java/de/juplo/facebook/exceptions/GraphApiErrorHandlerTest.java b/src/test/java/de/juplo/facebook/exceptions/GraphApiErrorHandlerTest.java
deleted file mode 100644 (file)
index 86e1dfd..0000000
+++ /dev/null
@@ -1,801 +0,0 @@
-package de.juplo.facebook.exceptions;
-
-import de.juplo.facebook.exceptions.GraphApiException.Type;
-import java.util.Date;
-import java.util.Map;
-import java.util.Set;
-import javax.annotation.Resource;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-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 Moritz
- */
-@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(new Integer(1), e.getCode());
-      assertEquals("An unknown error has occurred.", e.getMessage());
-      assertEquals(Type.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(new Integer(2), e.getCode());
-      assertEquals("An unexpected error has occurred. Please retry your request later.", e.getMessage());
-      assertEquals(Type.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(new Integer(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(Type.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(new Integer(100), e.getCode());
-      assertEquals("Unsupported get request.", e.getMessage());
-      assertEquals(Type.GraphMethodException, e.getType());
-    }
-  }
-
-  @Test
-  public void testError104()
-  {
-    log.info("testError104");
-
-    requestFactory.setBody("{\"error\":{\"message\":\"An access token is required to request this resource.\",\"type\":\"OAuthException\",\"code\":104,\"fbtrace_id\":\"E2Jjkj5++LL\"}}");
-
-    try
-    {
-      clientTemplate.getForObject("ANY", SOME.class);
-      fail("The expected exception was not thrown");
-    }
-    catch(AccessTokenRequiredException e)
-    {
-      log.debug("{}", e.toString());
-      assertEquals("invalid_request", e.getOAuth2ErrorCode());
-      assertEquals(new Integer(104), e.getCode());
-      assertEquals("An access token is required to request this resource.", e.getMessage());
-      assertEquals(Type.OAuthException, e.getType());
-      assertEquals("E2Jjkj5++LL", e.getTraceId());
-    }
-  }
-
-  @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(new Integer(613), e.getCode());
-      assertEquals("(#613) Calls to stream have exceeded the rate of 600 calls per 600 seconds.", e.getMessage());
-      assertEquals(Type.OAuthException, e.getType());
-    }
-  }
-
-  @Test
-  public void testError2200()
-  {
-    requestFactory.setBody("{\"error\":{\"message\":\"(#2200) callback verification failed: \",\"type\":\"OAuthException\",\"code\":2200,\"fbtrace_id\":\"ESLjoZKvPXg\"}}");
-    
-    try
-    {
-      clientTemplate.getForObject("ANY", SOME.class);
-      fail("The expected exception was not thrown");
-    }
-    catch(CallbackVerificationFailedException e)
-    {
-      log.debug("{}", e.toString());
-      assertEquals("invalid_request", e.getOAuth2ErrorCode());
-      assertEquals(new Integer(2200), e.getCode());
-      assertEquals("(#2200) callback verification failed: ", e.getMessage());
-      assertEquals(Type.OAuthException, e.getType());
-      assertEquals("ESLjoZKvPXg", e.getTraceId());
-    }
-  }
-
-  @Test
-  public void testUnmappedError()
-  {
-    log.info("testUnmappedError");
-
-
-    requestFactory.setBody(
-        "{\n" +
-        "  \"error\":\n" +
-        "  {\n" +
-        "    \"message\": \"This error does not exist.\",\n" +
-        "    \"type\": \"NonexistentTypeException\",\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(new Integer(999999999), e.getCode());
-      assertEquals("This error does not exist.", e.getMessage());
-      try
-      {
-        Type type = e.getType();
-        log.error("unknown type: {}", type);
-        fail("unmapped type was resolved by enum: " + type);
-      }
-      catch (IllegalArgumentException ee) {}
-    }
-  }
-
-  @Test
-  public void testUnmappedErrors()
-  {
-    log.info("testUnmappedErrors");
-
-
-    requestFactory.setBody(
-        "{\n" +
-        "  \"error\":\n" +
-        "  {\n" +
-        "    \"message\": null,\n" +
-        "    \"type\": \"WhateverTypeException\",\n" +
-        "    \"code\": 999999999\n" +
-        "  }\n" +
-        "}");
-
-    try
-    {
-      clientTemplate.getForObject("ANY", SOME.class);
-      fail("The expected exception was not thrown");
-    }
-    catch(UnmappedErrorException e)
-    {
-      log.debug("{}", e.toString());
-      assertNull(e.getMessage());
-      try
-      {
-        Type type = e.getType();
-        log.error("unknown type: {}", type);
-        fail("unmapped type was resolved by enum: " + type);
-      }
-      catch (IllegalArgumentException ee) {}
-      assertEquals(new Integer(999999999), e.getCode());
-      assertNull(e.getSubCode());
-      assertNull(e.getUserTitle());
-      assertNull(e.getUserMessage());
-      assertNull(e.getTraceId());
-    }
-    catch(Exception e)
-    {
-      fail("A wrong exception was thrown: " + e.toString());
-    }
-
-
-    requestFactory.setBody(
-        "{\n" +
-        "  \"error\":\n" +
-        "  {\n" +
-        "    \"type\": \"WhateverTypeException\",\n" +
-        "    \"code\": 999999999\n" +
-        "  }\n" +
-        "}");
-
-    try
-    {
-      clientTemplate.getForObject("ANY", SOME.class);
-      fail("The expected exception was not thrown");
-    }
-    catch(UnmappedErrorException e)
-    {
-      log.debug("{}", e.toString());
-      assertNull(e.getMessage());
-      try
-      {
-        Type type = e.getType();
-        log.error("unknown type: {}", type);
-        fail("unmapped type was resolved by enum: " + type);
-      }
-      catch (IllegalArgumentException ee) {}
-      assertEquals(new Integer(999999999), e.getCode());
-      assertNull(e.getSubCode());
-      assertNull(e.getUserTitle());
-      assertNull(e.getUserMessage());
-      assertNull(e.getTraceId());
-    }
-    catch(Exception e)
-    {
-      fail("A wrong exception was thrown: " + e.toString());
-    }
-
-
-    requestFactory.setBody(
-        "{\n" +
-        "  \"error\":\n" +
-        "  {\n" +
-        "    \"message\": \"An unmapped 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(UnmappedErrorException e)
-    {
-      log.debug("{}", e.toString());
-      assertEquals("An unmapped Graph-API-Exception.", e.getMessage());
-      assertNull(e.getType());
-      assertEquals(new Integer(999999999), e.getCode());
-      assertNull(e.getSubCode());
-      assertNull(e.getUserTitle());
-      assertNull(e.getUserMessage());
-      assertNull(e.getTraceId());
-    }
-    catch(Exception e)
-    {
-      fail("A wrong exception was thrown: " + e.toString());
-    }
-
-
-    requestFactory.setBody(
-        "{\n" +
-        "  \"error\":\n" +
-        "  {\n" +
-        "    \"message\": \"An unmapped Graph-API-Exception.\",\n" +
-        "    \"code\": 999999999\n" +
-        "  }\n" +
-        "}");
-
-    try
-    {
-      clientTemplate.getForObject("ANY", SOME.class);
-      fail("The expected exception was not thrown");
-    }
-    catch(UnmappedErrorException e)
-    {
-      log.debug("{}", e.toString());
-      assertEquals("An unmapped Graph-API-Exception.", e.getMessage());
-      assertNull(e.getType());
-      assertEquals(new Integer(999999999), e.getCode());
-      assertNull(e.getSubCode());
-      assertNull(e.getUserTitle());
-      assertNull(e.getUserMessage());
-      assertNull(e.getTraceId());
-    }
-    catch(Exception e)
-    {
-      fail("A wrong exception was thrown: " + e.toString());
-    }
-  }
-
-  @Test
-  public void testInvlalidErrors()
-  {
-    log.info("testInvalidErrors");
-
-
-    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
-  {
-  }
-}
diff --git a/src/test/java/de/juplo/facebook/exceptions/MockClientHttpRequestFactory.java b/src/test/java/de/juplo/facebook/exceptions/MockClientHttpRequestFactory.java
deleted file mode 100644 (file)
index 34dc793..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-package de.juplo.facebook.exceptions;
-
-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;
-    }
-  }
-}