001/**
002 * Copyright (c) 2010-2019 Mark Allen, Norbert Bartels.
003 *
004 * Permission is hereby granted, free of charge, to any person obtaining a copy
005 * of this software and associated documentation files (the "Software"), to deal
006 * in the Software without restriction, including without limitation the rights
007 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
008 * copies of the Software, and to permit persons to whom the Software is
009 * furnished to do so, subject to the following conditions:
010 *
011 * The above copyright notice and this permission notice shall be included in
012 * all copies or substantial portions of the Software.
013 *
014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
017 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
019 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
020 * THE SOFTWARE.
021 */
022package com.restfb.exception.generator;
023
024import static com.restfb.util.StringUtils.toInteger;
025
026import com.restfb.exception.*;
027import com.restfb.json.Json;
028import com.restfb.json.JsonObject;
029import com.restfb.json.ParseException;
030
031public class DefaultFacebookExceptionGenerator implements FacebookExceptionGenerator {
032
033  /**
034   * Knows how to map Graph API exceptions to formal Java exception types.
035   */
036  protected FacebookExceptionMapper graphFacebookExceptionMapper;
037
038  public DefaultFacebookExceptionGenerator() {
039    super();
040    graphFacebookExceptionMapper = createGraphFacebookExceptionMapper();
041  }
042
043  @Override
044  public void throwFacebookResponseStatusExceptionIfNecessary(String json, Integer httpStatusCode) {
045    try {
046      skipResponseStatusExceptionParsing(json);
047
048      // If we have a batch API exception, throw it.
049      throwBatchFacebookResponseStatusExceptionIfNecessary(json, httpStatusCode);
050
051      JsonObject errorObject = Json.parse(json).asObject();
052
053      if (errorObject.get(ERROR_ATTRIBUTE_NAME) == null) {
054        return;
055      }
056
057      ExceptionInformation container = createFacebookResponseTypeAndMessageContainer(errorObject, httpStatusCode);
058
059      throw graphFacebookExceptionMapper.exceptionForTypeAndMessage(container);
060    } catch (ParseException e) {
061      throw new FacebookJsonMappingException("Unable to process the Facebook API response", e);
062    } catch (ResponseErrorJsonParsingException ex) {
063      // do nothing here
064    }
065  }
066
067  protected ExceptionInformation createFacebookResponseTypeAndMessageContainer(JsonObject errorObject,
068      Integer httpStatusCode) {
069    JsonObject innerErrorObject = errorObject.get(ERROR_ATTRIBUTE_NAME).asObject();
070
071    // If there's an Integer error code, pluck it out.
072    Integer errorCode = innerErrorObject.get(ERROR_CODE_ATTRIBUTE_NAME) != null
073        ? toInteger(innerErrorObject.get(ERROR_CODE_ATTRIBUTE_NAME).toString()) : null;
074    Integer errorSubcode = innerErrorObject.get(ERROR_SUBCODE_ATTRIBUTE_NAME) != null
075        ? toInteger(innerErrorObject.get(ERROR_SUBCODE_ATTRIBUTE_NAME).toString()) : null;
076
077    return new ExceptionInformation(errorCode, errorSubcode, httpStatusCode,
078      innerErrorObject.getString(ERROR_TYPE_ATTRIBUTE_NAME, null),
079      innerErrorObject.get(ERROR_MESSAGE_ATTRIBUTE_NAME).asString(),
080      innerErrorObject.getString(ERROR_USER_TITLE_ATTRIBUTE_NAME, null),
081      innerErrorObject.getString(ERROR_USER_MSG_ATTRIBUTE_NAME, null),
082      innerErrorObject.getBoolean(ERROR_IS_TRANSIENT_NAME, false), errorObject);
083  }
084
085  @Override
086  public void throwBatchFacebookResponseStatusExceptionIfNecessary(String json, Integer httpStatusCode) {
087    try {
088      skipResponseStatusExceptionParsing(json);
089
090      JsonObject errorObject = silentlyCreateObjectFromString(json);
091
092      if (errorObject == null || errorObject.get(BATCH_ERROR_ATTRIBUTE_NAME) == null
093          || errorObject.get(BATCH_ERROR_DESCRIPTION_ATTRIBUTE_NAME) == null)
094        return;
095
096      ExceptionInformation container = new ExceptionInformation(errorObject.getInt(BATCH_ERROR_ATTRIBUTE_NAME, 0),
097        httpStatusCode, errorObject.getString(BATCH_ERROR_DESCRIPTION_ATTRIBUTE_NAME, null), errorObject);
098
099      throw graphFacebookExceptionMapper.exceptionForTypeAndMessage(container);
100    } catch (ParseException e) {
101      throw new FacebookJsonMappingException("Unable to process the Facebook API response", e);
102    } catch (ResponseErrorJsonParsingException ex) {
103      // do nothing here
104    }
105  }
106
107  /**
108   * Specifies how we map Graph API exception types/messages to real Java exceptions.
109   * <p>
110   * Uses an instance of {@link DefaultGraphFacebookExceptionMapper} by default.
111   *
112   * @return An instance of the exception mapper we should use.
113   * @since 1.6
114   */
115  protected FacebookExceptionMapper createGraphFacebookExceptionMapper() {
116    return new DefaultGraphFacebookExceptionMapper();
117  }
118
119  /**
120   * checks if a string may be a json and contains a error string somewhere, this is used for speedup the error parsing
121   *
122   * @param json
123   */
124  protected void skipResponseStatusExceptionParsing(String json) throws ResponseErrorJsonParsingException {
125    // If this is not an object, it's not an error response.
126    if (!json.startsWith("{")) {
127      throw new ResponseErrorJsonParsingException();
128    }
129
130    int subStrEnd = Math.min(50, json.length());
131    if (!json.substring(0, subStrEnd).contains("\"error\"")) {
132      throw new ResponseErrorJsonParsingException();
133    }
134  }
135
136  /**
137   * create a {@link JsonObject} from String and swallow possible JsonException
138   *
139   * @param json
140   *          the string representation of the json
141   * @return the JsonObject, may be <code>null</code>
142   */
143  protected JsonObject silentlyCreateObjectFromString(String json) {
144    JsonObject errorObject = null;
145
146    // We need to swallow exceptions here because it's possible to get a legit
147    // Facebook response that contains illegal JSON (e.g.
148    // users.getLoggedInUser returning 1240077) - we're only interested in
149    // whether or not there's an error_code field present.
150    try {
151      errorObject = Json.parse(json).asObject();
152    } catch (ParseException e) {
153      // do nothing here
154    }
155
156    return errorObject;
157  }
158
159  /**
160   * A canned implementation of {@link FacebookExceptionMapper} that maps Graph API exceptions.
161   * <p>
162   * Thanks to BatchFB's Jeff Schnitzer for doing some of the legwork to find these exception type names.
163   *
164   * @author <a href="http://restfb.com">Mark Allen</a>
165   * @since 1.6.3
166   */
167  protected static class DefaultGraphFacebookExceptionMapper implements FacebookExceptionMapper {
168
169    @Override
170    public FacebookException exceptionForTypeAndMessage(ExceptionInformation container) {
171      if ("OAuthException".equals(container.getType()) || "OAuthAccessTokenException".equals(container.getType())) {
172        return new FacebookOAuthException(container.getType(), container.getMessage(), container.getErrorCode(),
173          container.getErrorSubcode(), container.getHttpStatusCode(), container.getUserTitle(),
174          container.getUserMessage(), container.getIsTransient(), container.getRawError());
175      }
176
177      if ("QueryParseException".equals(container.getType())) {
178        return new FacebookQueryParseException(container.getType(), container.getMessage(), container.getErrorCode(),
179          container.getErrorSubcode(), container.getHttpStatusCode(), container.getUserTitle(),
180          container.getUserMessage(), container.getIsTransient(), container.getRawError());
181      }
182
183      // Don't recognize this exception type? Just go with the standard
184      // FacebookGraphException.
185      return new FacebookGraphException(container.getType(), container.getMessage(), container.getErrorCode(),
186        container.getErrorSubcode(), container.getHttpStatusCode(), container.getUserTitle(),
187        container.getUserMessage(), container.getIsTransient(), container.getRawError());
188    }
189  }
190}