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.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 java.util.ArrayList; 030import java.util.List; 031import java.util.Optional; 032import java.util.stream.Collectors; 033 034import com.restfb.Facebook; 035import com.restfb.Parameter; 036import com.restfb.util.ReflectionUtils; 037 038/** 039 * Encapsulates a discrete part of an entire 040 * <a href="https://developers.facebook.com/docs/reference/api/batch/" target="_blank">Facebook Batch API</a> request. 041 * <p> 042 * Must be constructed by {@link BatchRequestBuilder}. 043 * 044 * @author <a href="http://restfb.com">Mark Allen</a> 045 * @since 1.6.5 046 * @see BatchRequestBuilder 047 */ 048public class BatchRequest { 049 @Facebook 050 private String method; 051 052 @Facebook("relative_url") 053 private String relativeUrl; 054 055 @Facebook 056 private String body; 057 058 @Facebook("attached_files") 059 private String attachedFiles; 060 061 @Facebook("depends_on") 062 private String dependsOn; 063 064 @Facebook 065 private String name; 066 067 @Facebook("omit_response_on_success") 068 private boolean omitResponseOnSuccess; 069 070 @Facebook 071 private List<BatchHeader> headers; 072 073 /** 074 * Designed to be invoked by instances of <tt>{@link BatchRequestBuilder}</tt> . 075 * 076 * @param relativeUrl 077 * The endpoint to hit, for example {@code "me/friends"}. 078 * @param parameters 079 * Optional list of URL parameters to be added to the value specified in {@code relativeUrl}. 080 * @param method 081 * The HTTP method to use, for example {@code "GET"}. 082 * @param headers 083 * The list of HTTP headers for the request. 084 * @param bodyParameters 085 * The parameters that comprise the request body, for example {@code "message=Test status update"} . 086 * @param attachedFiles 087 * Names of any attached files for this call, for example {@code "cat1, cat2"}. 088 * @param name 089 * The logical name of this request, for example {@code "get-friends"}. 090 * @param dependsOn 091 * If this call depends on the completion of another call in the current batch, for example 092 * {@code "get-friends"}. 093 * @param omitResponseOnSuccess 094 * To make sure FB returns JSON in the event that this request completes successfully, set this to 095 * {@code false}. 096 * @throws IllegalArgumentException 097 * If {@code relativeUrl} is {@code null}. 098 */ 099 protected BatchRequest(String relativeUrl, List<Parameter> parameters, String method, List<BatchHeader> headers, 100 List<Parameter> bodyParameters, String attachedFiles, String dependsOn, String name, 101 boolean omitResponseOnSuccess) { 102 103 this.relativeUrl = Optional.ofNullable(relativeUrl) 104 .orElseThrow(() -> new IllegalArgumentException("The 'relativeUrl' parameter is required.")); 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 return parameters.stream().map(p -> urlEncode(p.name) + "=" + urlEncode(p.value)).collect(Collectors.joining("&")); 284 } 285 286 @Override 287 public int hashCode() { 288 return ReflectionUtils.hashCode(this); 289 } 290 291 @Override 292 public boolean equals(Object that) { 293 return ReflectionUtils.equals(this, that); 294 } 295 296 @Override 297 public String toString() { 298 return ReflectionUtils.toString(this); 299 } 300 301 /** 302 * The HTTP method to use, for example {@code "GET"}. 303 * 304 * @return The HTTP method to use. 305 */ 306 public String getMethod() { 307 return method; 308 } 309 310 /** 311 * The endpoint to hit, for example {@code "me/friends?limit=10"}. 312 * 313 * @return The endpoint to hit. 314 */ 315 public String getRelativeUrl() { 316 return relativeUrl; 317 } 318 319 /** 320 * The request body, for example {@code "message=Test status update"}. 321 * 322 * @return The request body. 323 */ 324 public String getBody() { 325 return body; 326 } 327 328 /** 329 * Names of any attached files for this call, for example {@code "cat1, cat2"} . 330 * 331 * @return Names of any attached files for this call. 332 */ 333 public String getAttachedFiles() { 334 return attachedFiles; 335 } 336 337 /** 338 * The logical name for this request, for example {@code "get-friends"}. 339 * 340 * @return The logical name for this request. 341 */ 342 public String getName() { 343 return name; 344 } 345 346 /** 347 * Another call in the current batch upon which this call depends, for example {@code "get-friends"}. 348 * 349 * @return Another call in the current batch upon which this call depends. 350 */ 351 public String getDependsOn() { 352 return dependsOn; 353 } 354 355 /** 356 * Will the batch response for this request be {@code null}? 357 * 358 * @return {@code true} if the batch response for this request will be {@code null}, {@code false} otherwise. 359 */ 360 public boolean isOmitResponseOnSuccess() { 361 return omitResponseOnSuccess; 362 } 363 364 /** 365 * HTTP Headers to be sent as part of this request. 366 * 367 * @return HTTP Headers to be sent as part of this request. 368 */ 369 public List<BatchHeader> getHeaders() { 370 return unmodifiableList(headers); 371 } 372}