001/*
002 * Copyright (c) 2010-2024 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;
023
024import static com.restfb.util.StringUtils.isBlank;
025import static com.restfb.util.StringUtils.trimToEmpty;
026import static java.lang.String.format;
027
028import java.io.IOException;
029import java.util.ArrayList;
030import java.util.List;
031import java.util.Optional;
032
033import com.restfb.types.FacebookReelAttachment;
034import com.restfb.util.StringUtils;
035
036/**
037 * Specifies how a class that sends {@code HTTP} requests to the Facebook API endpoint must operate.
038 * 
039 * @author <a href="http://restfb.com">Mark Allen</a>
040 */
041public interface WebRequestor {
042  /**
043   * Encapsulates an HTTP response body and status code.
044   * 
045   * @author <a href="http://restfb.com">Mark Allen</a>
046   */
047  class Response {
048    /**
049     * HTTP response status code (e.g. 200).
050     */
051    private final Integer statusCode;
052
053    /**
054     * HTTP response body as text.
055     */
056    private final String body;
057
058    /**
059     * Creates a response with the given HTTP status code and response body as text.
060     * 
061     * @param statusCode
062     *          The HTTP status code of the response.
063     * @param body
064     *          The response body as text.
065     */
066    public Response(Integer statusCode, String body) {
067      this.statusCode = statusCode;
068      this.body = trimToEmpty(body);
069    }
070
071    /**
072     * Gets the HTTP status code.
073     * 
074     * @return The HTTP status code.
075     */
076    public Integer getStatusCode() {
077      return statusCode;
078    }
079
080    /**
081     * Gets the HTTP response body as text.
082     * 
083     * @return The HTTP response body as text.
084     */
085    public String getBody() {
086      return body;
087    }
088
089    /**
090     * @see java.lang.Object#toString()
091     */
092    @Override
093    public String toString() {
094      if (isBlank(getBody())) {
095        return format("HTTP status code %d and an empty response body.", getStatusCode());
096      }
097      return format("HTTP status code %d and response body: %s", getStatusCode(), getBody());
098    }
099  }
100
101  /**
102   * encapsulates the HTTP Request configuration
103   */
104  class Request {
105
106    private final String url;
107
108    private final Optional<String> headerAccessToken;
109
110    private String parameters;
111
112    private Body body;
113
114    private List<BinaryAttachment> binaryAttachments;
115
116    /**
117     * Simple http request with url and a header access token
118     * 
119     * @param url
120     *          the endpoint the request ist directed to
121     * @param headerAccessToken
122     *          the HTTP header access token (may be {@code null})
123     */
124    public Request(String url, String headerAccessToken) {
125      this(url, headerAccessToken, null);
126    }
127
128    /**
129     * Simple http request with url and a header access token
130     *
131     * @param url
132     *          the endpoint the request ist directed to
133     * @param headerAccessToken
134     *          the HTTP header access token (may be {@code null})
135     * @param parameters
136     *          the query parameter string
137     */
138    public Request(String url, String headerAccessToken, String parameters) {
139      this(url, headerAccessToken, parameters, null);
140    }
141
142    /**
143     * Simple http request with url and a header access token
144     *
145     * @param url
146     *          the endpoint the request ist directed to
147     * @param headerAccessToken
148     *          the HTTP header access token (may be {@code null})
149     * @param parameters
150     *          the query parameter string
151     * @param attachments
152     *          list of binary attachments
153     */
154    public Request(String url, String headerAccessToken, String parameters, List<BinaryAttachment> attachments) {
155      this.url = url;
156      this.headerAccessToken = Optional.ofNullable(headerAccessToken);
157      this.parameters = parameters;
158      setBinaryAttachments(attachments);
159    }
160
161    public String getUrl() {
162      return url;
163    }
164
165    public String getHeaderAccessToken() {
166      return headerAccessToken.orElse(null);
167    }
168
169    public boolean hasHeaderAccessToken() {
170      return headerAccessToken.isPresent();
171    }
172
173    public String getParameters() {
174      return parameters;
175    }
176
177    public List<BinaryAttachment> getBinaryAttachments() {
178      return Optional.ofNullable(binaryAttachments).orElse(new ArrayList<>());
179    }
180
181    public void setBinaryAttachments(List<BinaryAttachment> binaryAttachments) {
182      this.binaryAttachments = Optional.ofNullable(binaryAttachments).orElse(new ArrayList<>());
183    }
184
185    public String getFullUrl() {
186      if (!StringUtils.isBlank(parameters)) {
187        if (url != null && url.contains("?")) {
188          return url + "&" + parameters;
189        }
190        return url + "?" + parameters;
191      }
192      return url;
193    }
194
195    @Override
196    public String toString() {
197      return format("Request to url %s with parameters %s. Header access token: %b", getUrl(), getParameters(),
198        hasHeaderAccessToken());
199    }
200
201    public void setBody(Body body) {
202      this.body = body;
203    }
204
205    public Body getBody() {
206      return body;
207    }
208
209    public boolean hasBody() {
210      return body != null;
211    }
212
213    public boolean isReelUpload() {
214      List<BinaryAttachment> attachments = getBinaryAttachments();
215      if (attachments.size() == 1) {
216        return attachments.get(0).isFacebookReel();
217      }
218
219      return false;
220    }
221
222    public Optional<FacebookReelAttachment> getReel() {
223      if (isReelUpload()) {
224        return Optional.of((FacebookReelAttachment) getBinaryAttachments().get(0));
225      }
226
227      return Optional.empty();
228    }
229  }
230
231  /**
232   * Given a Facebook API endpoint URL, execute a {@code GET} against it.
233   * 
234   * @param request
235   *          The request data for the {@code GET} request
236   * @return HTTP response data.
237   * @throws IOException
238   *           If an error occurs while performing the {@code GET} operation.
239   * @since 1.5
240   */
241  Response executeGet(Request request) throws IOException;
242
243  /**
244   * Given a Facebook API endpoint URL and parameter string, execute a {@code POST} to the endpoint URL.
245   * 
246   * @param request
247   *          The request data used for the {@code POST} request.
248   * @return HTTP response data.
249   * @throws IOException
250   *           If an error occurs while performing the {@code POST}.
251   */
252  Response executePost(Request request) throws IOException;
253
254  /**
255   * Given a Facebook API endpoint URL and parameter string, execute a {@code DELETE} to the endpoint URL.
256   * 
257   * @param request
258   *          The request data used for the {@code DELETE} request.
259   * @return HTTP response data.
260   * @throws IOException
261   *           If an error occurs while performing the {@code DELETE}.
262   */
263  Response executeDelete(Request request) throws IOException;
264
265  /**
266   * Provides access to the facebook header information.
267   * <p>
268   * The fields <code>x-fb-rev</code>, <code>x-fb-trace-id</code> and <code>x-fb-debug</code> are checked and returned
269   * in a single container of the type {@link DebugHeaderInfo}
270   * 
271   * @return container with the explained facebook debug header information
272   */
273  DebugHeaderInfo getDebugHeaderInfo();
274}