001/* 002 * Copyright (c) 2010-2021 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 031import java.util.Optional; 032import java.util.regex.Matcher; 033import java.util.regex.Pattern; 034 035public class DefaultFacebookExceptionGenerator implements FacebookExceptionGenerator { 036 037 /** 038 * Knows how to map Graph API exceptions to formal Java exception types. 039 */ 040 protected FacebookExceptionMapper graphFacebookExceptionMapper; 041 042 private static final Pattern ERROR_PATTERN = Pattern.compile("\"error\"\\s*:"); 043 044 public DefaultFacebookExceptionGenerator() { 045 super(); 046 graphFacebookExceptionMapper = createGraphFacebookExceptionMapper(); 047 } 048 049 @Override 050 public void throwFacebookResponseStatusExceptionIfNecessary(String json, Integer httpStatusCode) { 051 try { 052 skipResponseStatusExceptionParsing(json); 053 054 // If we have a batch API exception, throw it. 055 throwBatchFacebookResponseStatusExceptionIfNecessary(json, httpStatusCode); 056 057 JsonObject errorObject = Json.parse(json).asObject(); 058 059 if (!errorObject.contains(ERROR_ATTRIBUTE_NAME)) { 060 return; 061 } 062 063 ExceptionInformation container = createFacebookResponseTypeAndMessageContainer(errorObject, httpStatusCode); 064 065 throw graphFacebookExceptionMapper.exceptionForTypeAndMessage(container); 066 } catch (ParseException e) { 067 throw new FacebookJsonMappingException("Unable to process the Facebook API response", e); 068 } catch (ResponseErrorJsonParsingException ex) { 069 // do nothing here 070 } 071 } 072 073 protected ExceptionInformation createFacebookResponseTypeAndMessageContainer(JsonObject errorObject, 074 Integer httpStatusCode) { 075 JsonObject innerErrorObject = errorObject.get(ERROR_ATTRIBUTE_NAME).asObject(); 076 077 // If there's an Integer error code, pluck it out. 078 Integer errorCode = Optional.ofNullable(innerErrorObject.get(ERROR_CODE_ATTRIBUTE_NAME)).map(obj -> toInteger(obj.toString())).orElse(null); 079 Integer errorSubcode = Optional.ofNullable(innerErrorObject.get(ERROR_SUBCODE_ATTRIBUTE_NAME)).map(obj -> toInteger(obj.toString())).orElse(null); 080 081 return new ExceptionInformation(errorCode, errorSubcode, httpStatusCode, 082 innerErrorObject.getString(ERROR_TYPE_ATTRIBUTE_NAME, null), 083 innerErrorObject.get(ERROR_MESSAGE_ATTRIBUTE_NAME).asString(), 084 innerErrorObject.getString(ERROR_USER_TITLE_ATTRIBUTE_NAME, null), 085 innerErrorObject.getString(ERROR_USER_MSG_ATTRIBUTE_NAME, null), 086 innerErrorObject.getBoolean(ERROR_IS_TRANSIENT_NAME, false), errorObject); 087 } 088 089 @Override 090 public void throwBatchFacebookResponseStatusExceptionIfNecessary(String json, Integer httpStatusCode) { 091 try { 092 skipResponseStatusExceptionParsing(json); 093 094 JsonObject errorObject = silentlyCreateObjectFromString(json); 095 096 if (errorObject == null || errorObject.contains(BATCH_ERROR_ATTRIBUTE_NAME) 097 || errorObject.contains(BATCH_ERROR_DESCRIPTION_ATTRIBUTE_NAME) 098 // not a batch response, if data key is present 099 || errorObject.contains("data")) 100 return; 101 102 ExceptionInformation container = new ExceptionInformation(errorObject.getInt(BATCH_ERROR_ATTRIBUTE_NAME, 0), 103 httpStatusCode, errorObject.getString(BATCH_ERROR_DESCRIPTION_ATTRIBUTE_NAME, null), errorObject); 104 105 throw graphFacebookExceptionMapper.exceptionForTypeAndMessage(container); 106 } catch (ParseException e) { 107 throw new FacebookJsonMappingException("Unable to process the Facebook API response", e); 108 } catch (ResponseErrorJsonParsingException ex) { 109 // do nothing here 110 } 111 } 112 113 /** 114 * Specifies how we map Graph API exception types/messages to real Java exceptions. 115 * <p> 116 * Uses an instance of {@link DefaultGraphFacebookExceptionMapper} by default. 117 * 118 * @return An instance of the exception mapper we should use. 119 * @since 1.6 120 */ 121 protected FacebookExceptionMapper createGraphFacebookExceptionMapper() { 122 return new DefaultGraphFacebookExceptionMapper(); 123 } 124 125 /** 126 * checks if a string may be a json and contains a error string somewhere, this is used for speedup the error parsing 127 * 128 * @param json 129 */ 130 protected void skipResponseStatusExceptionParsing(String json) throws ResponseErrorJsonParsingException { 131 // If this is not an object, it's not an error response. 132 if (!json.startsWith("{")) { 133 throw new ResponseErrorJsonParsingException(); 134 } 135 136 int subStrEnd = Math.min(50, json.length()); 137 Matcher matcher = ERROR_PATTERN.matcher(json.substring(0, subStrEnd)); 138 if (!matcher.find()) { 139 throw new ResponseErrorJsonParsingException(); 140 } 141 } 142 143 /** 144 * create a {@link JsonObject} from String and swallow possible JsonException 145 * 146 * @param json 147 * the string representation of the json 148 * @return the JsonObject, may be <code>null</code> 149 */ 150 protected JsonObject silentlyCreateObjectFromString(String json) { 151 JsonObject errorObject = null; 152 153 // We need to swallow exceptions here because it's possible to get a legit 154 // Facebook response that contains illegal JSON (e.g. 155 // users.getLoggedInUser returning 1240077) - we're only interested in 156 // whether or not there's an error_code field present. 157 try { 158 errorObject = Json.parse(json).asObject(); 159 } catch (ParseException e) { 160 // do nothing here 161 } 162 163 return errorObject; 164 } 165 166 /** 167 * A canned implementation of {@link FacebookExceptionMapper} that maps Graph API exceptions. 168 * <p> 169 * Thanks to BatchFB's Jeff Schnitzer for doing some of the legwork to find these exception type names. 170 * 171 * @author <a href="http://restfb.com">Mark Allen</a> 172 * @since 1.6.3 173 */ 174 protected static class DefaultGraphFacebookExceptionMapper implements FacebookExceptionMapper { 175 176 @Override 177 public FacebookException exceptionForTypeAndMessage(ExceptionInformation container) { 178 if ("OAuthException".equals(container.getType()) || "OAuthAccessTokenException".equals(container.getType())) { 179 return new FacebookOAuthException(container.getType(), container.getMessage(), container.getErrorCode(), 180 container.getErrorSubcode(), container.getHttpStatusCode(), container.getUserTitle(), 181 container.getUserMessage(), container.getIsTransient(), container.getRawError()); 182 } 183 184 if ("QueryParseException".equals(container.getType())) { 185 return new FacebookQueryParseException(container.getType(), container.getMessage(), container.getErrorCode(), 186 container.getErrorSubcode(), container.getHttpStatusCode(), container.getUserTitle(), 187 container.getUserMessage(), container.getIsTransient(), container.getRawError()); 188 } 189 190 // Don't recognize this exception type? Just go with the standard 191 // FacebookGraphException. 192 return new FacebookGraphException(container.getType(), container.getMessage(), container.getErrorCode(), 193 container.getErrorSubcode(), container.getHttpStatusCode(), container.getUserTitle(), 194 container.getUserMessage(), container.getIsTransient(), container.getRawError()); 195 } 196 } 197}