Refactoring: moved FacebookErrorMessage in its own file/class
[facebook-utils] / src / main / java / de / juplo / facebook / client / GraphApiErrorHandler.java
index faff55e..6b1ab97 100644 (file)
@@ -4,74 +4,96 @@ import de.juplo.facebook.exceptions.GraphApiException;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.List;
+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.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;
+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.
  *
- * @author kai
+ * @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 extends OAuth2ErrorHandler
+public class GraphApiErrorHandler implements ResponseErrorHandler
 {
-  private final OAuth2ErrorHandler errorHandler;
-  private List<HttpMessageConverter<?>> messageConverters =
-      new RestTemplate().getMessageConverters();
+  private final static Logger LOG =
+      LoggerFactory.getLogger(GraphApiErrorHandler.class);
 
-
-  public GraphApiErrorHandler(OAuth2ErrorHandler errorHandler)
-  {
-    super(null);
-    this.errorHandler = errorHandler;
-  }
+  private final ResponseErrorHandler parent;
 
 
-       /**
-        * @param messageConverters the messageConverters to set
-        */
-  @Override
-  public void setMessageConverters(
-      List<HttpMessageConverter<?>> messageConverters
-      )
+  public GraphApiErrorHandler(ResponseErrorHandler errorHandler)
   {
-    this.messageConverters = messageConverters;
-    errorHandler.setMessageConverters(messageConverters);
+    this.parent = errorHandler;
   }
 
+
   @Override
   public boolean hasError(ClientHttpResponse response) throws IOException
   {
     return
         HttpStatus.Series.CLIENT_ERROR.equals(response.getStatusCode().series())
-        || this.errorHandler.hasError(response);
+        || this.parent.hasError(response);
   }
 
   @Override
   public void handleError(final ClientHttpResponse response) throws IOException
   {
-    if (!HttpStatus.Series.CLIENT_ERROR.equals(response.getStatusCode().series()))
+    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)
     {
-      // We should only care about 400 level errors. Ex: A 500 server error shouldn't
-      // be an oauth related error.
-      errorHandler.handleError(response);
+      // 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;
     }
-    else
+
+    final byte[] body = FileCopyUtils.copyToByteArray(response.getBody());
+    GraphApiException error;
+
+    try
     {
-      // Need to use buffered response because input stream may need to be consumed multiple times.
-      ClientHttpResponse bufferedResponse = new ClientHttpResponse()
-      {
-        private byte[] lazyBody;
+      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
         {
@@ -81,16 +103,7 @@ public class GraphApiErrorHandler extends OAuth2ErrorHandler
         @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);
+          return new ByteArrayInputStream(body);
         }
 
         @Override
@@ -118,31 +131,10 @@ public class GraphApiErrorHandler extends OAuth2ErrorHandler
         }
       };
 
-
-      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);
+      parent.handleError(buffered);
+      return;
     }
+
+    throw error;
   }
 }