001/* 002 * Copyright (c) 2010-2026 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; 023 024import java.io.Serializable; 025import java.net.URLDecoder; 026import java.nio.charset.StandardCharsets; 027import java.time.Duration; 028import java.util.Optional; 029import java.util.StringJoiner; 030 031/** 032 * Root of the RestFB exception hierarchy. 033 * 034 * @author <a href="http://restfb.com">Mark Allen</a> 035 * @author <a href="Http://restfb.com">Norbert Bartels</a> 036 */ 037public abstract class FacebookException extends RuntimeException { 038 private static final long serialVersionUID = 1L; 039 040 private InfoData infoData; 041 042 /** 043 * Creates an exception with the given message. 044 * 045 * @param message 046 * A message describing this exception. 047 */ 048 protected FacebookException(String message) { 049 super(message); 050 } 051 052 /** 053 * Creates an exception with the given message and cause. 054 * 055 * @param message 056 * A message describing this exception. 057 * @param cause 058 * The exception that caused this exception to be thrown. 059 */ 060 protected FacebookException(String message, Throwable cause) { 061 super(message, cause); 062 } 063 064 /** 065 * Adds optional request metadata to the exception for diagnostics. 066 * 067 * @param infoData 068 * encapsulated request metadata (may be {@code null}) 069 * @return this exception for fluent usage. 070 */ 071 public FacebookException withInfoData(InfoData infoData) { 072 this.infoData = infoData; 073 return this; 074 } 075 076 /** 077 * Convenience helper to attach request metadata. 078 * 079 * @return this exception for fluent usage. 080 */ 081 public FacebookException withInfoData(String httpMethod, String fullEndpoint, String parameterString, 082 String headerAccessToken, Long startTime) { 083 return withInfoData(new InfoData(httpMethod, fullEndpoint, parameterString, headerAccessToken, startTime)); 084 } 085 086 @Override 087 public String getMessage() { 088 return super.getMessage() + getInfoData().map(s -> ", " + s).orElse(""); 089 } 090 091 public String getBasicMessage() { 092 return super.getMessage(); 093 } 094 095 /** 096 * Request metadata associated with this exception, if available. 097 * 098 * @return optional request metadata 099 */ 100 public Optional<InfoData> getInfoData() { 101 return Optional.ofNullable(infoData); 102 } 103 104 /** 105 * Aggregates optional metadata that may help diagnose a failing Facebook request. 106 */ 107 public static final class InfoData implements Serializable { 108 private static final long serialVersionUID = 1L; 109 110 private final String httpMethod; 111 private final String fullEndpoint; 112 private final String parameterString; 113 private final String headerAccessToken; 114 private final Long startTime; 115 private final long endTime; 116 117 public InfoData(String httpMethod, String fullEndpoint, String parameterString, String headerAccessToken, 118 Long startTime) { 119 this.httpMethod = httpMethod; 120 this.fullEndpoint = fullEndpoint; 121 this.parameterString = parameterString; 122 this.headerAccessToken = headerAccessToken; 123 this.startTime = startTime; 124 this.endTime = System.currentTimeMillis(); 125 } 126 127 public String getHttpMethod() { 128 return httpMethod; 129 } 130 131 public String getFullEndpoint() { 132 return fullEndpoint; 133 } 134 135 public String getParameterString() { 136 return parameterString; 137 } 138 139 public String getHeaderAccessToken() { 140 return headerAccessToken; 141 } 142 143 public Duration getDuration() { 144 if (startTime == null) { 145 return Duration.ZERO; 146 } 147 return Duration.ofMillis(Math.max(0, endTime - startTime)); 148 } 149 150 public String getDurationAsString() { 151 return startTime == null ? "unknown" : getDuration().toMillis() + "ms"; 152 } 153 154 public String getUrl() { 155 String parameterStringToReturn; 156 try { 157 parameterStringToReturn = URLDecoder.decode(parameterString, StandardCharsets.UTF_8); 158 } catch (Exception e) { 159 parameterStringToReturn = parameterString; 160 } 161 String endpointToReturn; 162 try { 163 endpointToReturn = URLDecoder.decode(fullEndpoint, StandardCharsets.UTF_8); 164 } catch (Exception e) { 165 endpointToReturn = fullEndpoint; 166 } 167 return endpointToReturn + (parameterString != null ? "?" + parameterStringToReturn : ""); 168 } 169 170 @Override 171 public String toString() { 172 return "URL: " + httpMethod + ": " + getUrl() 173 + (headerAccessToken != null ? " headerAccessToken: " + headerAccessToken : "") + ", response-time: " 174 + getDurationAsString(); 175 } 176 177 } 178}