/** * Copyright 2010-present Facebook. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.facebook; import android.graphics.Bitmap; import android.location.Location; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.ParcelFileDescriptor; import android.text.TextUtils; import android.util.Pair; import com.facebook.internal.ServerProtocol; import com.facebook.model.*; import com.facebook.internal.Logger; import com.facebook.internal.Utility; import com.facebook.internal.Validate; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.*; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.text.SimpleDateFormat; import java.util.*; import java.util.Map.Entry; /** * A single request to be sent to the Facebook Platform through either the Graph API or REST API. The Request class provides functionality * relating to serializing and deserializing requests and responses, making calls in batches (with a single round-trip * to the service) and making calls asynchronously. * * The particular service endpoint that a request targets is determined by either a graph path (see the * {@link #setGraphPath(String) setGraphPath} method) or a REST method name (see the {@link #setRestMethod(String) * setRestMethod} method); a single request may not target both. * * A Request can be executed either anonymously or representing an authenticated user. In the former case, no Session * needs to be specified, while in the latter, a Session that is in an opened state must be provided. If requests are * executed in a batch, a Facebook application ID must be associated with the batch, either by supplying a Session for * at least one of the requests in the batch (the first one found in the batch will be used) or by calling the * {@link #setDefaultBatchApplicationId(String) setDefaultBatchApplicationId} method. * * After completion of a request, its Session, if any, will be checked to determine if its Facebook access token needs * to be extended; if so, a request to extend it will be issued in the background. */ public class Request { /** * The maximum number of requests that can be submitted in a single batch. This limit is enforced on the service * side by the Facebook platform, not by the Request class. */ public static final int MAXIMUM_BATCH_SIZE = 50; private static final String ME = "me"; private static final String MY_FRIENDS = "me/friends"; private static final String MY_PHOTOS = "me/photos"; private static final String MY_VIDEOS = "me/videos"; private static final String SEARCH = "search"; private static final String MY_FEED = "me/feed"; private static final String USER_AGENT_BASE = "FBAndroidSDK"; private static final String USER_AGENT_HEADER = "User-Agent"; private static final String CONTENT_TYPE_HEADER = "Content-Type"; // Parameter names/values private static final String PICTURE_PARAM = "picture"; private static final String FORMAT_PARAM = "format"; private static final String FORMAT_JSON = "json"; private static final String SDK_PARAM = "sdk"; private static final String SDK_ANDROID = "android"; private static final String ACCESS_TOKEN_PARAM = "access_token"; private static final String BATCH_ENTRY_NAME_PARAM = "name"; private static final String BATCH_ENTRY_OMIT_RESPONSE_ON_SUCCESS_PARAM = "omit_response_on_success"; private static final String BATCH_ENTRY_DEPENDS_ON_PARAM = "depends_on"; private static final String BATCH_APP_ID_PARAM = "batch_app_id"; private static final String BATCH_RELATIVE_URL_PARAM = "relative_url"; private static final String BATCH_BODY_PARAM = "body"; private static final String BATCH_METHOD_PARAM = "method"; private static final String BATCH_PARAM = "batch"; private static final String ATTACHMENT_FILENAME_PREFIX = "file"; private static final String ATTACHED_FILES_PARAM = "attached_files"; private static final String MIGRATION_BUNDLE_PARAM = "migration_bundle"; private static final String ISO_8601_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ssZ"; private static final String MIME_BOUNDARY = "3i2ndDfv2rTHiSisAbouNdArYfORhtTPEefj3q2f"; private static String defaultBatchApplicationId; private Session session; private HttpMethod httpMethod; private String graphPath; private GraphObject graphObject; private String restMethod; private String batchEntryName; private String batchEntryDependsOn; private boolean batchEntryOmitResultOnSuccess = true; private Bundle parameters; private Callback callback; private String overriddenURL; /** * Constructs a request without a session, graph path, or any other parameters. */ public Request() { this(null, null, null, null, null); } /** * Constructs a request with a Session to retrieve a particular graph path. A Session need not be provided, in which * case the request is sent without an access token and thus is not executed in the context of any particular user. * Only certain graph requests can be expected to succeed in this case. If a Session is provided, it must be in an * opened state or the request will fail. * * @param session * the Session to use, or null * @param graphPath * the graph path to retrieve */ public Request(Session session, String graphPath) { this(session, graphPath, null, null, null); } /** * Constructs a request with a specific Session, graph path, parameters, and HTTP method. A Session need not be * provided, in which case the request is sent without an access token and thus is not executed in the context of * any particular user. Only certain graph requests can be expected to succeed in this case. If a Session is * provided, it must be in an opened state or the request will fail. * * Depending on the httpMethod parameter, the object at the graph path may be retrieved, created, or deleted. * * @param session * the Session to use, or null * @param graphPath * the graph path to retrieve, create, or delete * @param parameters * additional parameters to pass along with the Graph API request; parameters must be Strings, Numbers, * Bitmaps, Dates, or Byte arrays. * @param httpMethod * the {@link HttpMethod} to use for the request, or null for default (HttpMethod.GET) */ public Request(Session session, String graphPath, Bundle parameters, HttpMethod httpMethod) { this(session, graphPath, parameters, httpMethod, null); } /** * Constructs a request with a specific Session, graph path, parameters, and HTTP method. A Session need not be * provided, in which case the request is sent without an access token and thus is not executed in the context of * any particular user. Only certain graph requests can be expected to succeed in this case. If a Session is * provided, it must be in an opened state or the request will fail. * * Depending on the httpMethod parameter, the object at the graph path may be retrieved, created, or deleted. * * @param session * the Session to use, or null * @param graphPath * the graph path to retrieve, create, or delete * @param parameters * additional parameters to pass along with the Graph API request; parameters must be Strings, Numbers, * Bitmaps, Dates, or Byte arrays. * @param httpMethod * the {@link HttpMethod} to use for the request, or null for default (HttpMethod.GET) * @param callback * a callback that will be called when the request is completed to handle success or error conditions */ public Request(Session session, String graphPath, Bundle parameters, HttpMethod httpMethod, Callback callback) { this.session = session; this.graphPath = graphPath; this.callback = callback; setHttpMethod(httpMethod); if (parameters != null) { this.parameters = new Bundle(parameters); } else { this.parameters = new Bundle(); } if (!this.parameters.containsKey(MIGRATION_BUNDLE_PARAM)) { this.parameters.putString(MIGRATION_BUNDLE_PARAM, FacebookSdkVersion.MIGRATION_BUNDLE); } } Request(Session session, URL overriddenURL) { this.session = session; this.overriddenURL = overriddenURL.toString(); setHttpMethod(HttpMethod.GET); this.parameters = new Bundle(); } /** * Creates a new Request configured to post a GraphObject to a particular graph path, to either create or update the * object at that path. * * @param session * the Session to use, or null; if non-null, the session must be in an opened state * @param graphPath * the graph path to retrieve, create, or delete * @param graphObject * the GraphObject to create or update * @param callback * a callback that will be called when the request is completed to handle success or error conditions * @return a Request that is ready to execute */ public static Request newPostRequest(Session session, String graphPath, GraphObject graphObject, Callback callback) { Request request = new Request(session, graphPath, null, HttpMethod.POST , callback); request.setGraphObject(graphObject); return request; } /** * Creates a new Request configured to make a call to the Facebook REST API. * * @param session * the Session to use, or null; if non-null, the session must be in an opened state * @param restMethod * the method in the Facebook REST API to execute * @param parameters * additional parameters to pass along with the Graph API request; parameters must be Strings, Numbers, * Bitmaps, Dates, or Byte arrays. * @param httpMethod * the HTTP method to use for the request; must be one of GET, POST, or DELETE * @return a Request that is ready to execute */ public static Request newRestRequest(Session session, String restMethod, Bundle parameters, HttpMethod httpMethod) { Request request = new Request(session, null, parameters, httpMethod); request.setRestMethod(restMethod); return request; } /** * Creates a new Request configured to retrieve a user's own profile. * * @param session * the Session to use, or null; if non-null, the session must be in an opened state * @param callback * a callback that will be called when the request is completed to handle success or error conditions * @return a Request that is ready to execute */ public static Request newMeRequest(Session session, final GraphUserCallback callback) { Callback wrapper = new Callback() { @Override public void onCompleted(Response response) { if (callback != null) { callback.onCompleted(response.getGraphObjectAs(GraphUser.class), response); } } }; return new Request(session, ME, null, null, wrapper); } /** * Creates a new Request configured to retrieve a user's friend list. * * @param session * the Session to use, or null; if non-null, the session must be in an opened state * @param callback * a callback that will be called when the request is completed to handle success or error conditions * @return a Request that is ready to execute */ public static Request newMyFriendsRequest(Session session, final GraphUserListCallback callback) { Callback wrapper = new Callback() { @Override public void onCompleted(Response response) { if (callback != null) { callback.onCompleted(typedListFromResponse(response, GraphUser.class), response); } } }; return new Request(session, MY_FRIENDS, null, null, wrapper); } /** * Creates a new Request configured to upload a photo to the user's default photo album. * * @param session * the Session to use, or null; if non-null, the session must be in an opened state * @param image * the image to upload * @param callback * a callback that will be called when the request is completed to handle success or error conditions * @return a Request that is ready to execute */ public static Request newUploadPhotoRequest(Session session, Bitmap image, Callback callback) { Bundle parameters = new Bundle(1); parameters.putParcelable(PICTURE_PARAM, image); return new Request(session, MY_PHOTOS, parameters, HttpMethod.POST, callback); } /** * Creates a new Request configured to upload a photo to the user's default photo album. The photo * will be read from the specified stream. * * @param session the Session to use, or null; if non-null, the session must be in an opened state * @param file the file containing the photo to upload * @param callback a callback that will be called when the request is completed to handle success or error conditions * @return a Request that is ready to execute */ public static Request newUploadPhotoRequest(Session session, File file, Callback callback) throws FileNotFoundException { ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); Bundle parameters = new Bundle(1); parameters.putParcelable(PICTURE_PARAM, descriptor); return new Request(session, MY_PHOTOS, parameters, HttpMethod.POST, callback); } /** * Creates a new Request configured to upload a photo to the user's default photo album. The photo * will be read from the specified file descriptor. * * @param session the Session to use, or null; if non-null, the session must be in an opened state * @param file the file to upload * @param callback a callback that will be called when the request is completed to handle success or error conditions * @return a Request that is ready to execute */ public static Request newUploadVideoRequest(Session session, File file, Callback callback) throws FileNotFoundException { ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); Bundle parameters = new Bundle(1); parameters.putParcelable(file.getName(), descriptor); return new Request(session, MY_VIDEOS, parameters, HttpMethod.POST, callback); } /** * Creates a new Request configured to retrieve a particular graph path. * * @param session * the Session to use, or null; if non-null, the session must be in an opened state * @param graphPath * the graph path to retrieve * @param callback * a callback that will be called when the request is completed to handle success or error conditions * @return a Request that is ready to execute */ public static Request newGraphPathRequest(Session session, String graphPath, Callback callback) { return new Request(session, graphPath, null, null, callback); } /** * Creates a new Request that is configured to perform a search for places near a specified location via the Graph * API. At least one of location or searchText must be specified. * * @param session * the Session to use, or null; if non-null, the session must be in an opened state * @param location * the location around which to search; only the latitude and longitude components of the location are * meaningful * @param radiusInMeters * the radius around the location to search, specified in meters; this is ignored if * no location is specified * @param resultsLimit * the maximum number of results to return * @param searchText * optional text to search for as part of the name or type of an object * @param callback * a callback that will be called when the request is completed to handle success or error conditions * @return a Request that is ready to execute * * @throws FacebookException If neither location nor searchText is specified */ public static Request newPlacesSearchRequest(Session session, Location location, int radiusInMeters, int resultsLimit, String searchText, final GraphPlaceListCallback callback) { if (location == null && Utility.isNullOrEmpty(searchText)) { throw new FacebookException("Either location or searchText must be specified."); } Bundle parameters = new Bundle(5); parameters.putString("type", "place"); parameters.putInt("limit", resultsLimit); if (location != null) { parameters.putString("center", String.format(Locale.US, "%f,%f", location.getLatitude(), location.getLongitude())); parameters.putInt("distance", radiusInMeters); } if (!Utility.isNullOrEmpty(searchText)) { parameters.putString("q", searchText); } Callback wrapper = new Callback() { @Override public void onCompleted(Response response) { if (callback != null) { callback.onCompleted(typedListFromResponse(response, GraphPlace.class), response); } } }; return new Request(session, SEARCH, parameters, HttpMethod.GET, wrapper); } /** * Creates a new Request configured to post a status update to a user's feed. * * @param session * the Session to use, or null; if non-null, the session must be in an opened state * @param message * the text of the status update * @param callback * a callback that will be called when the request is completed to handle success or error conditions * @return a Request that is ready to execute */ public static Request newStatusUpdateRequest(Session session, String message, Callback callback) { Bundle parameters = new Bundle(); parameters.putString("message", message); return new Request(session, MY_FEED, parameters, HttpMethod.POST, callback); } /** * Returns the GraphObject, if any, associated with this request. * * @return the GraphObject associated with this requeset, or null if there is none */ public final GraphObject getGraphObject() { return this.graphObject; } /** * Sets the GraphObject associated with this request. This is meaningful only for POST requests. * * @param graphObject * the GraphObject to upload along with this request */ public final void setGraphObject(GraphObject graphObject) { this.graphObject = graphObject; } /** * Returns the graph path of this request, if any. * * @return the graph path of this request, or null if there is none */ public final String getGraphPath() { return this.graphPath; } /** * Sets the graph path of this request. A graph path may not be set if a REST method has been specified. * * @param graphPath * the graph path for this request */ public final void setGraphPath(String graphPath) { this.graphPath = graphPath; } /** * Returns the {@link HttpMethod} to use for this request. * * @return the HttpMethod */ public final HttpMethod getHttpMethod() { return this.httpMethod; } /** * Sets the {@link HttpMethod} to use for this request. * * @param httpMethod * the HttpMethod, or null for the default (HttpMethod.GET). */ public final void setHttpMethod(HttpMethod httpMethod) { if (overriddenURL != null && httpMethod != HttpMethod.GET) { throw new FacebookException("Can't change HTTP method on request with overridden URL."); } this.httpMethod = (httpMethod != null) ? httpMethod : HttpMethod.GET; } /** * Returns the parameters for this request. * * @return the parameters */ public final Bundle getParameters() { return this.parameters; } /** * Sets the parameters for this request. * * @param parameters * the parameters */ public final void setParameters(Bundle parameters) { this.parameters = parameters; } /** * Returns the REST method to call for this request. * * @return the REST method */ public final String getRestMethod() { return this.restMethod; } /** * Sets the REST method to call for this request. A REST method may not be set if a graph path has been specified. * * @param restMethod * the REST method to call */ public final void setRestMethod(String restMethod) { this.restMethod = restMethod; } /** * Returns the Session associated with this request. * * @return the Session associated with this request, or null if none has been specified */ public final Session getSession() { return this.session; } /** * Sets the Session to use for this request. The Session does not need to be opened at the time it is specified, but * it must be opened by the time the request is executed. * * @param session * the Session to use for this request */ public final void setSession(Session session) { this.session = session; } /** * Returns the name of this request's entry in a batched request. * * @return the name of this request's batch entry, or null if none has been specified */ public final String getBatchEntryName() { return this.batchEntryName; } /** * Sets the name of this request's entry in a batched request. This value is only used if this request is submitted * as part of a batched request. It can be used to specified dependencies between requests. See Batch Requests in the Graph API * documentation for more details. * * @param batchEntryName * the name of this request's entry in a batched request, which must be unique within a particular batch * of requests */ public final void setBatchEntryName(String batchEntryName) { this.batchEntryName = batchEntryName; } /** * Returns the name of the request that this request entry explicitly depends on in a batched request. * * @return the name of this request's dependency, or null if none has been specified */ public final String getBatchEntryDependsOn() { return this.batchEntryDependsOn; } /** * Sets the name of the request entry that this request explicitly depends on in a batched request. This value is * only used if this request is submitted as part of a batched request. It can be used to specified dependencies * between requests. See Batch Requests in * the Graph API documentation for more details. * * @param batchEntryDependsOn * the name of the request entry that this entry depends on in a batched request */ public final void setBatchEntryDependsOn(String batchEntryDependsOn) { this.batchEntryDependsOn = batchEntryDependsOn; } /** * Returns whether or not this batch entry will return a response if it is successful. Only applies if another * request entry in the batch specifies this entry as a dependency. * * @return the name of this request's dependency, or null if none has been specified */ public final boolean getBatchEntryOmitResultOnSuccess() { return this.batchEntryOmitResultOnSuccess; } /** * Sets whether or not this batch entry will return a response if it is successful. Only applies if another * request entry in the batch specifies this entry as a dependency. See * Batch Requests in the Graph API * documentation for more details. * * @param batchEntryOmitResultOnSuccess * the name of the request entry that this entry depends on in a batched request */ public final void setBatchEntryOmitResultOnSuccess(boolean batchEntryOmitResultOnSuccess) { this.batchEntryOmitResultOnSuccess = batchEntryOmitResultOnSuccess; } /** * Gets the default Facebook application ID that will be used to submit batched requests if none of those requests * specifies a Session. Batched requests require an application ID, so either at least one request in a batch must * specify a Session or the application ID must be specified explicitly. * * @return the Facebook application ID to use for batched requests if none can be determined */ public static final String getDefaultBatchApplicationId() { return Request.defaultBatchApplicationId; } /** * Sets the default application ID that will be used to submit batched requests if none of those requests specifies * a Session. Batched requests require an application ID, so either at least one request in a batch must specify a * Session or the application ID must be specified explicitly. * * @param applicationId * the Facebook application ID to use for batched requests if none can be determined */ public static final void setDefaultBatchApplicationId(String applicationId) { Request.defaultBatchApplicationId = applicationId; } /** * Returns the callback which will be called when the request finishes. * * @return the callback */ public final Callback getCallback() { return callback; } /** * Sets the callback which will be called when the request finishes. * * @param callback * the callback */ public final void setCallback(Callback callback) { this.callback = callback; } /** * Starts a new Request configured to post a GraphObject to a particular graph path, to either create or update the * object at that path. *
* This should only be called from the UI thread. * * @param session * the Session to use, or null; if non-null, the session must be in an opened state * @param graphPath * the graph path to retrieve, create, or delete * @param graphObject * the GraphObject to create or update * @param callback * a callback that will be called when the request is completed to handle success or error conditions * @return a RequestAsyncTask that is executing the request */ public static RequestAsyncTask executePostRequestAsync(Session session, String graphPath, GraphObject graphObject, Callback callback) { return newPostRequest(session, graphPath, graphObject, callback).executeAsync(); } /** * Creates a new Request configured to make a call to the Facebook REST API. * * This should only be called from the UI thread. * * @param session * the Session to use, or null; if non-null, the session must be in an opened state * @param restMethod * the method in the Facebook REST API to execute * @param parameters * additional parameters to pass along with the Graph API request; parameters must be Strings, Numbers, * Bitmaps, Dates, or Byte arrays. * @param httpMethod * the HTTP method to use for the request; must be one of GET, POST, or DELETE * @return a RequestAsyncTask that is executing the request */ public static RequestAsyncTask executeRestRequestAsync(Session session, String restMethod, Bundle parameters, HttpMethod httpMethod) { return newRestRequest(session, restMethod, parameters, httpMethod).executeAsync(); } /** * Creates a new Request configured to retrieve a user's own profile. * * This should only be called from the UI thread. * * @param session * the Session to use, or null; if non-null, the session must be in an opened state * @param callback * a callback that will be called when the request is completed to handle success or error conditions * @return a RequestAsyncTask that is executing the request */ public static RequestAsyncTask executeMeRequestAsync(Session session, GraphUserCallback callback) { return newMeRequest(session, callback).executeAsync(); } /** * Creates a new Request configured to retrieve a user's friend list. * * This should only be called from the UI thread. * * @param session * the Session to use, or null; if non-null, the session must be in an opened state * @param callback * a callback that will be called when the request is completed to handle success or error conditions * @return a RequestAsyncTask that is executing the request */ public static RequestAsyncTask executeMyFriendsRequestAsync(Session session, GraphUserListCallback callback) { return newMyFriendsRequest(session, callback).executeAsync(); } /** * Creates a new Request configured to upload a photo to the user's default photo album. * * This should only be called from the UI thread. * * @param session * the Session to use, or null; if non-null, the session must be in an opened state * @param image * the image to upload * @param callback * a callback that will be called when the request is completed to handle success or error conditions * @return a RequestAsyncTask that is executing the request */ public static RequestAsyncTask executeUploadPhotoRequestAsync(Session session, Bitmap image, Callback callback) { return newUploadPhotoRequest(session, image, callback).executeAsync(); } /** * Creates a new Request configured to upload a photo to the user's default photo album. The photo * will be read from the specified stream. * * This should only be called from the UI thread. * * @param session the Session to use, or null; if non-null, the session must be in an opened state * @param file the file containing the photo to upload * @param callback a callback that will be called when the request is completed to handle success or error conditions * @return a RequestAsyncTask that is executing the request */ public static RequestAsyncTask executeUploadPhotoRequestAsync(Session session, File file, Callback callback) throws FileNotFoundException { return newUploadPhotoRequest(session, file, callback).executeAsync(); } /** * Creates a new Request configured to retrieve a particular graph path. * * This should only be called from the UI thread. * * @param session * the Session to use, or null; if non-null, the session must be in an opened state * @param graphPath * the graph path to retrieve * @param callback * a callback that will be called when the request is completed to handle success or error conditions * @return a RequestAsyncTask that is executing the request */ public static RequestAsyncTask executeGraphPathRequestAsync(Session session, String graphPath, Callback callback) { return newGraphPathRequest(session, graphPath, callback).executeAsync(); } /** * Creates a new Request that is configured to perform a search for places near a specified location via the Graph * API. * * This should only be called from the UI thread. * * @param session * the Session to use, or null; if non-null, the session must be in an opened state * @param location * the location around which to search; only the latitude and longitude components of the location are * meaningful * @param radiusInMeters * the radius around the location to search, specified in meters * @param resultsLimit * the maximum number of results to return * @param searchText * optional text to search for as part of the name or type of an object * @param callback * a callback that will be called when the request is completed to handle success or error conditions * @return a RequestAsyncTask that is executing the request * * @throws FacebookException If neither location nor searchText is specified */ public static RequestAsyncTask executePlacesSearchRequestAsync(Session session, Location location, int radiusInMeters, int resultsLimit, String searchText, GraphPlaceListCallback callback) { return newPlacesSearchRequest(session, location, radiusInMeters, resultsLimit, searchText, callback).executeAsync(); } /** * Creates a new Request configured to post a status update to a user's feed. * * This should only be called from the UI thread. * * @param session * the Session to use, or null; if non-null, the session must be in an opened state * @param message * the text of the status update * @param callback * a callback that will be called when the request is completed to handle success or error conditions * @return a RequestAsyncTask that is executing the request */ public static RequestAsyncTask executeStatusUpdateRequestAsync(Session session, String message, Callback callback) { return newStatusUpdateRequest(session, message, callback).executeAsync(); } /** * Executes this request and returns the response. * * This should only be called if you have transitioned off the UI thread. * * @return the Response object representing the results of the request * * @throws FacebookException * If there was an error in the protocol used to communicate with the service * @throws IllegalArgumentException */ public final Response executeAndWait() { return Request.executeAndWait(this); } /** * Executes this request and returns the response. * * This should only be called from the UI thread. * * @return a RequestAsyncTask that is executing the request * * @throws IllegalArgumentException */ public final RequestAsyncTask executeAsync() { return Request.executeBatchAsync(this); } /** * Serializes one or more requests but does not execute them. The resulting HttpURLConnection can be executed * explicitly by the caller. * * @param requests * one or more Requests to serialize * @return an HttpURLConnection which is ready to execute * * @throws FacebookException * If any of the requests in the batch are badly constructed or if there are problems * contacting the service * @throws IllegalArgumentException if the passed in array is zero-length * @throws NullPointerException if the passed in array or any of its contents are null */ public static HttpURLConnection toHttpConnection(Request... requests) { return toHttpConnection(Arrays.asList(requests)); } /** * Serializes one or more requests but does not execute them. The resulting HttpURLConnection can be executed * explicitly by the caller. * * @param requests * one or more Requests to serialize * @return an HttpURLConnection which is ready to execute * * @throws FacebookException * If any of the requests in the batch are badly constructed or if there are problems * contacting the service * @throws IllegalArgumentException if the passed in collection is empty * @throws NullPointerException if the passed in collection or any of its contents are null */ public static HttpURLConnection toHttpConnection(Collection