001/* 002 * Copyright (c) 2010-2014 Mark Allen. 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 */ 022 023package com.restfb.batch; 024 025import static com.restfb.util.StringUtils.isBlank; 026import static com.restfb.util.UrlUtils.urlEncode; 027import static java.lang.String.format; 028import static java.util.Arrays.asList; 029import static java.util.Collections.unmodifiableList; 030 031import java.util.ArrayList; 032import java.util.List; 033 034import com.restfb.Facebook; 035import com.restfb.Parameter; 036import com.restfb.util.ReflectionUtils; 037 038/** 039 * Encapsulates a discrete part of an entire <a href="https://developers.facebook.com/docs/reference/api/batch/" 040 * 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 = new ArrayList<BatchHeader>(); 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} or blank. 098 */ 099 protected BatchRequest(String relativeUrl, List<Parameter> parameters, String method, List<BatchHeader> headers, 100 List<Parameter> bodyParameters, String attachedFiles, String dependsOn, String name, boolean omitResponseOnSuccess) { 101 if (isBlank(relativeUrl)) 102 throw new IllegalArgumentException("The 'relativeUrl' parameter is required."); 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.size() > 0) 113 this.relativeUrl = 114 format(this.relativeUrl.indexOf("?") == -1 ? "%s?%s" : "%s&%s", this.relativeUrl, 115 generateParameterString(parameters)); 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<Parameter>(); 133 private List<BatchHeader> headers = new ArrayList<BatchHeader>(); 134 private List<Parameter> bodyParameters = new ArrayList<Parameter>(); 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 StringBuilder parameterStringBuilder = new StringBuilder(); 283 boolean first = true; 284 285 for (Parameter parameter : parameters) { 286 if (first) 287 first = false; 288 else 289 parameterStringBuilder.append("&"); 290 291 parameterStringBuilder.append(urlEncode(parameter.name)); 292 parameterStringBuilder.append("="); 293 parameterStringBuilder.append(urlEncode(parameter.value)); 294 } 295 296 return parameterStringBuilder.toString(); 297 } 298 299 /** 300 * @see java.lang.Object#hashCode() 301 */ 302 @Override 303 public int hashCode() { 304 return ReflectionUtils.hashCode(this); 305 } 306 307 /** 308 * @see java.lang.Object#equals(java.lang.Object) 309 */ 310 @Override 311 public boolean equals(Object that) { 312 return ReflectionUtils.equals(this, that); 313 } 314 315 /** 316 * @see java.lang.Object#toString() 317 */ 318 @Override 319 public String toString() { 320 return ReflectionUtils.toString(this); 321 } 322 323 /** 324 * The HTTP method to use, for example {@code "GET"}. 325 * 326 * @return The HTTP method to use. 327 */ 328 public String getMethod() { 329 return method; 330 } 331 332 /** 333 * The endpoint to hit, for example {@code "me/friends?limit=10"}. 334 * 335 * @return The endpoint to hit. 336 */ 337 public String getRelativeUrl() { 338 return relativeUrl; 339 } 340 341 /** 342 * The request body, for example {@code "message=Test status update"}. 343 * 344 * @return The request body. 345 */ 346 public String getBody() { 347 return body; 348 } 349 350 /** 351 * Names of any attached files for this call, for example {@code "cat1, cat2"} . 352 * 353 * @return Names of any attached files for this call. 354 */ 355 public String getAttachedFiles() { 356 return attachedFiles; 357 } 358 359 /** 360 * The logical name for this request, for example {@code "get-friends"}. 361 * 362 * @return The logical name for this request. 363 */ 364 public String getName() { 365 return name; 366 } 367 368 /** 369 * Another call in the current batch upon which this call depends, for example {@code "get-friends"}. 370 * 371 * @return Another call in the current batch upon which this call depends. 372 */ 373 public String getDependsOn() { 374 return dependsOn; 375 } 376 377 /** 378 * Will the batch response for this request be {@code null}? 379 * 380 * @return {@code true} if the batch response for this request will be {@code null}, {@code false} otherwise. 381 */ 382 public boolean isOmitResponseOnSuccess() { 383 return omitResponseOnSuccess; 384 } 385 386 /** 387 * HTTP Headers to be sent as part of this request. 388 * 389 * @return HTTP Headers to be sent as part of this request. 390 */ 391 public List<BatchHeader> getHeaders() { 392 return unmodifiableList(headers); 393 } 394}