Siehe:
[facebook-errors] / src / main / java / de / juplo / facebook / exceptions / GraphApiException.java
index 03aa627..a01a9e1 100644 (file)
@@ -1,5 +1,22 @@
 package de.juplo.facebook.exceptions;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.annotation.JsonRootName;
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.SerializableString;
+import com.fasterxml.jackson.core.io.CharacterEscapes;
+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 java.io.OutputStream;
+import java.nio.ByteBuffer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
 
 /**
@@ -7,56 +24,266 @@ import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
  * 
  * @author Kai Moritz
  */
-@org.codehaus.jackson.map.annotate.JsonDeserialize(using = GraphApiExceptionJackson1Deserializer.class)
-@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = GraphApiExceptionJackson2Deserializer.class)
 public class GraphApiException extends OAuth2Exception
 {
-  private final String type;
-  private final int code;
+  public enum Type { OAuthException, GraphMethodException }
 
-  private int httpErrorCode;
 
+  final static Logger LOG = LoggerFactory.getLogger(GraphApiException.class);
+  final static ObjectMapper OBJECT_MAPPER;
 
-  public GraphApiException(String message, String type, int code)
+  private final FacebookErrorMessage error;
+
+
+  static
   {
-    super(message);
-    this.type = type;
-    this.code = code;
+    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);
+    OBJECT_MAPPER.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
   }
 
 
-  public String getType()
+  public static GraphApiException create(InputStream in)
+      throws
+        IOException,
+        JsonParseException,
+        JsonMappingException
   {
-    return type;
+    return create(OBJECT_MAPPER.readValue(in, FacebookErrorMessage.class));
   }
 
-  public int getCode()
+  public static GraphApiException create(byte[] message)
+      throws
+        IOException,
+        JsonParseException,
+        JsonMappingException
   {
-    return code;
+    return create(OBJECT_MAPPER.readValue(message, FacebookErrorMessage.class));
   }
 
-  @Override
-  public int getHttpErrorCode()
+  public static GraphApiException create(FacebookErrorMessage error)
   {
-    return httpErrorCode == 0 ? super.getHttpErrorCode() : httpErrorCode;
+    // 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);
+    }
   }
 
-  public void setHttpErrorCode(int httpErrorCode)
+
+  protected GraphApiException(FacebookErrorMessage error)
   {
-    this.httpErrorCode = httpErrorCode;
+    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()
   {
-    StringBuilder builder = new StringBuilder();
-    builder.append("{error:{\"message\":\"");
-    builder.append(getMessage().replaceAll("\"", "\\\""));
-    builder.append("\",\"type\":");
-    builder.append(type.replaceAll("\"", "\\\""));
-    builder.append("\",\"code\":");
-    builder.append(code);
-    builder.append("}}");
-    return builder.toString();
+    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();
+    }
+  }
+
+
+  /**
+   * 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>
+   */
+  @JsonRootName("error")
+  @JsonPropertyOrder({ "message", "type", "code", "error_subcode", "error_user_title", "error_user_msg", "fbtrace_id" })
+  public static 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;
+  }
+
+  public static class CustomCharacterEscapes extends CharacterEscapes
+  {
+    private final int[] _asciiEscapes;
+
+
+    public CustomCharacterEscapes()
+    {
+      _asciiEscapes = standardAsciiEscapesForJSON();
+      _asciiEscapes['/'] = CharacterEscapes.ESCAPE_CUSTOM;
+    }
+
+
+    @Override
+    public int[] getEscapeCodesForAscii()
+    {
+        return _asciiEscapes;
+    }
+
+    @Override
+    public SerializableString getEscapeSequence(int i)
+    {
+      if(i == '/')
+      {
+        return new SerializableString()
+        {
+          @Override
+          public String getValue()
+          {
+            return "\\/";
+          }
+
+          @Override
+          public int charLength()
+          {
+            return 2;
+          }
+
+          @Override
+          public char[] asQuotedChars()
+          {
+            return new char[]{'\\','/'};
+          }
+
+          @Override
+          public byte[] asUnquotedUTF8()
+          {
+            return new byte[]{'\\','/'};
+          }
+
+          @Override
+          public byte[] asQuotedUTF8()
+          {
+            return new byte[]{'\\','/'};
+          }
+
+          @Override
+          public int appendQuotedUTF8(byte[] buffer, int offset)
+          {
+            throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+          }
+
+          @Override
+          public int appendQuoted(char[] buffer, int offset)
+          {
+            throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+          }
+
+          @Override
+          public int appendUnquotedUTF8(byte[] buffer, int offset)
+          {
+            throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+          }
+
+          @Override
+          public int appendUnquoted(char[] buffer, int offset)
+          {
+            throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+          }
+
+          @Override
+          public int writeQuotedUTF8(OutputStream out) throws IOException
+          {
+            throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+          }
+
+          @Override
+          public int writeUnquotedUTF8(OutputStream out) throws IOException
+          {
+            throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+          }
+
+          @Override
+          public int putQuotedUTF8(ByteBuffer buffer) throws IOException
+          {
+            throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+          }
+
+          @Override
+          public int putUnquotedUTF8(ByteBuffer out) throws IOException
+          {
+            throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+          }
+        };
+      }
+      else
+      {
+        return null;
+      }
+    }
   }
 }