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.batch;
023
024import static com.restfb.util.UrlUtils.urlEncode;
025import static java.lang.String.format;
026import static java.util.Arrays.asList;
027import static java.util.Collections.unmodifiableList;
028
029import com.restfb.Facebook;
030import com.restfb.Parameter;
031import com.restfb.util.ReflectionUtils;
032
033import java.util.ArrayList;
034import java.util.List;
035
036/**
037 * Encapsulates a discrete part of an entire
038 * <a href="https://developers.facebook.com/docs/reference/api/batch/" target="_blank">Facebook Batch API</a> request.
039 * <p>
040 * Must be constructed by {@link BatchRequestBuilder}.
041 * 
042 * @author <a href="http://restfb.com">Mark Allen</a>
043 * @since 1.6.5
044 * @see BatchRequestBuilder
045 */
046public class BatchRequest {
047  @Facebook
048  private String method;
049
050  @Facebook("relative_url")
051  private String relativeUrl;
052
053  @Facebook
054  private String body;
055
056  @Facebook("attached_files")
057  private String attachedFiles;
058
059  @Facebook("depends_on")
060  private String dependsOn;
061
062  @Facebook
063  private String name;
064
065  @Facebook("omit_response_on_success")
066  private boolean omitResponseOnSuccess;
067
068  @Facebook
069  private List<BatchHeader> headers;
070
071  /**
072   * Designed to be invoked by instances of <tt>{@link BatchRequestBuilder}</tt> .
073   * 
074   * @param relativeUrl
075   *          The endpoint to hit, for example {@code "me/friends"}.
076   * @param parameters
077   *          Optional list of URL parameters to be added to the value specified in {@code relativeUrl}.
078   * @param method
079   *          The HTTP method to use, for example {@code "GET"}.
080   * @param headers
081   *          The list of HTTP headers for the request.
082   * @param bodyParameters
083   *          The parameters that comprise the request body, for example {@code "message=Test status update"} .
084   * @param attachedFiles
085   *          Names of any attached files for this call, for example {@code "cat1, cat2"}.
086   * @param name
087   *          The logical name of this request, for example {@code "get-friends"}.
088   * @param dependsOn
089   *          If this call depends on the completion of another call in the current batch, for example
090   *          {@code "get-friends"}.
091   * @param omitResponseOnSuccess
092   *          To make sure FB returns JSON in the event that this request completes successfully, set this to
093   *          {@code false}.
094   * @throws IllegalArgumentException
095   *           If {@code relativeUrl} is {@code null}.
096   */
097  protected BatchRequest(String relativeUrl, List<Parameter> parameters, String method, List<BatchHeader> headers,
098      List<Parameter> bodyParameters, String attachedFiles, String dependsOn, String name,
099      boolean omitResponseOnSuccess) {
100    if (relativeUrl == null) {
101      throw new IllegalArgumentException("The 'relativeUrl' parameter is required.");
102    }
103
104    this.relativeUrl = relativeUrl;
105    this.method = method;
106    this.headers = headers;
107    this.attachedFiles = attachedFiles;
108    this.dependsOn = dependsOn;
109    this.name = name;
110    this.omitResponseOnSuccess = omitResponseOnSuccess;
111
112    if (!parameters.isEmpty()) {
113      this.relativeUrl = format(!this.relativeUrl.contains("?") ? "%s?%s" : "%s&%s", this.relativeUrl,
114        generateParameterString(parameters));
115    }
116
117    this.body = generateParameterString(bodyParameters);
118  }
119
120  /**
121   * Builder pattern implementation used to construct instances of <tt>{@link BatchRequest}</tt>.
122   * <p>
123   * See the <a href="https://developers.facebook.com/docs/reference/api/batch/" target="_blank">Facebook Batch API
124   * documentation</a> for more details on what a batch request looks like.
125   * 
126   * @author <a href="http://restfb.com">Mark Allen</a>
127   * @since 1.6.5
128   */
129  public static class BatchRequestBuilder {
130    private String method = "GET";
131    private String relativeUrl;
132    private List<Parameter> parameters = new ArrayList<>();
133    private List<BatchHeader> headers = new ArrayList<>();
134    private List<Parameter> bodyParameters = new ArrayList<>();
135    private String attachedFiles;
136    private String dependsOn;
137    private String name;
138    private boolean omitResponseOnSuccess;
139
140    /**
141     * Creates a batch request builder using the provided FB endpoint.
142     * <p>
143     * You can explicitly specify URL parameters here, or use {@link #parameters(Parameter...)} instead if you prefer to
144     * have the query string constructed programmatically.
145     * 
146     * @param relativeUrl
147     *          The endpoint to hit, for example {@code "me/friends"}.
148     */
149    public BatchRequestBuilder(String relativeUrl) {
150      this.relativeUrl = relativeUrl;
151    }
152
153    /**
154     * Sets the HTTP method for the request generated by this builder, for example {@code "POST"} ({@code GET} is the
155     * default value for this builder).
156     * 
157     * @param method
158     *          The HTTP method.
159     * @return This builder.
160     */
161    public BatchRequestBuilder method(String method) {
162      this.method = method;
163      return this;
164    }
165
166    /**
167     * Sets the logical name for the request generated by this builder. Useful for specifying dependencies between
168     * operations - the generated request can be referenced by name.
169     * 
170     * @param name
171     *          The logical name of the request generated by this builder.
172     * @return This builder.
173     */
174    public BatchRequestBuilder name(String name) {
175      this.name = name;
176      return this;
177    }
178
179    /**
180     * Sets the list of HTTP headers for the request generated by this builder.
181     * 
182     * @param headers
183     *          The HTTP headers.
184     * @return This builder.
185     */
186    public BatchRequestBuilder headers(BatchHeader... headers) {
187      this.headers.clear();
188      this.headers.addAll(asList(headers));
189      return this;
190    }
191
192    /**
193     * Sets the request body parameters for the request generated by this builder, for example
194     * {@code Parameter.with("message", "Test status update")}.
195     * 
196     * @param parameters
197     *          The request body parameters.
198     * @return This builder.
199     */
200    public BatchRequestBuilder body(Parameter... parameters) {
201      this.bodyParameters.clear();
202      this.bodyParameters.addAll(asList(parameters));
203      return this;
204    }
205
206    /**
207     * Sets the comma-delimited names of any attached files for this builder, for example {@code "cat1, cat2"}.
208     * 
209     * @param attachedFiles
210     *          The names of any attached files for this builder.
211     * @return This builder.
212     */
213    public BatchRequestBuilder attachedFiles(String attachedFiles) {
214      this.attachedFiles = attachedFiles;
215      return this;
216    }
217
218    /**
219     * Specifies if the request generated by this builder depends on the completion of another call in the current
220     * batch, for example {@code "first"}.
221     * 
222     * @param dependsOn
223     *          A reference to another request in the batch that this builder's request depends on.
224     * @return This builder.
225     */
226    public BatchRequestBuilder dependsOn(String dependsOn) {
227      this.dependsOn = dependsOn;
228      return this;
229    }
230
231    /**
232     * To make sure FB returns JSON in the event that this builder's request completes successfully, set this to
233     * {@code false}.
234     * 
235     * @param omitResponseOnSuccess
236     *          Set this to {@code false} to make sure FB returns JSON in the event that this builder's request
237     *          completes successfully,
238     * @return This builder.
239     */
240    public BatchRequestBuilder omitResponseOnSuccess(boolean omitResponseOnSuccess) {
241      this.omitResponseOnSuccess = omitResponseOnSuccess;
242      return this;
243    }
244
245    /**
246     * Specifies URL parameters for the request generated by this builder.
247     * 
248     * @param parameters
249     *          The URL parameters.
250     * @return This builder.
251     */
252    public BatchRequestBuilder parameters(Parameter... parameters) {
253      this.parameters.clear();
254      this.parameters.addAll(asList(parameters));
255      return this;
256    }
257
258    /**
259     * Generates an instance of {@link BatchRequest}.
260     * 
261     * @return An instance of {@link BatchRequest}.
262     */
263    public BatchRequest build() {
264      return new BatchRequest(relativeUrl, parameters, method, headers, bodyParameters, attachedFiles, dependsOn, name,
265        omitResponseOnSuccess);
266    }
267  }
268
269  /**
270   * For a list of parameters, generate a URL query string.
271   * <p>
272   * Does not include a leading "?" character.
273   * 
274   * @param parameters
275   *          The parameters to stringify.
276   * @return A URL query string representation of the given {@code parameters}.
277   */
278  protected String generateParameterString(List<Parameter> parameters) {
279    if (parameters == null) {
280      return "";
281    }
282
283    StringBuilder parameterStringBuilder = new StringBuilder();
284    boolean first = true;
285
286    for (Parameter parameter : parameters) {
287      if (first) {
288        first = false;
289      } else {
290        parameterStringBuilder.append("&");
291      }
292
293      parameterStringBuilder.append(urlEncode(parameter.name));
294      parameterStringBuilder.append("=");
295      parameterStringBuilder.append(urlEncode(parameter.value));
296    }
297
298    return parameterStringBuilder.toString();
299  }
300
301  @Override
302  public int hashCode() {
303    return ReflectionUtils.hashCode(this);
304  }
305
306  @Override
307  public boolean equals(Object that) {
308    return ReflectionUtils.equals(this, that);
309  }
310
311  @Override
312  public String toString() {
313    return ReflectionUtils.toString(this);
314  }
315
316  /**
317   * The HTTP method to use, for example {@code "GET"}.
318   * 
319   * @return The HTTP method to use.
320   */
321  public String getMethod() {
322    return method;
323  }
324
325  /**
326   * The endpoint to hit, for example {@code "me/friends?limit=10"}.
327   * 
328   * @return The endpoint to hit.
329   */
330  public String getRelativeUrl() {
331    return relativeUrl;
332  }
333
334  /**
335   * The request body, for example {@code "message=Test status update"}.
336   * 
337   * @return The request body.
338   */
339  public String getBody() {
340    return body;
341  }
342
343  /**
344   * Names of any attached files for this call, for example {@code "cat1, cat2"} .
345   * 
346   * @return Names of any attached files for this call.
347   */
348  public String getAttachedFiles() {
349    return attachedFiles;
350  }
351
352  /**
353   * The logical name for this request, for example {@code "get-friends"}.
354   * 
355   * @return The logical name for this request.
356   */
357  public String getName() {
358    return name;
359  }
360
361  /**
362   * Another call in the current batch upon which this call depends, for example {@code "get-friends"}.
363   * 
364   * @return Another call in the current batch upon which this call depends.
365   */
366  public String getDependsOn() {
367    return dependsOn;
368  }
369
370  /**
371   * Will the batch response for this request be {@code null}?
372   * 
373   * @return {@code true} if the batch response for this request will be {@code null}, {@code false} otherwise.
374   */
375  public boolean isOmitResponseOnSuccess() {
376    return omitResponseOnSuccess;
377  }
378
379  /**
380   * HTTP Headers to be sent as part of this request.
381   * 
382   * @return HTTP Headers to be sent as part of this request.
383   */
384  public List<BatchHeader> getHeaders() {
385    return unmodifiableList(headers);
386  }
387}