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}