Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ Then just invoke `gradle runAccessAsAppUser` to run the AccessAsAppUser example!

Note: The JCE bundled with oracle JRE supports keys upto 128 bit length only. To use larger cryptographic keys, install [JCE Unlimited Strength Jurisdiction Policy Files](http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html).

#### BatchRequestExample

There migt be cases where you want a bunch of requests to be executed as a single request. This example will show you how to do that

Just invoke `gradle runBatchExample` to run the BatchRequestExample example!

Building
--------

Expand Down
24 changes: 15 additions & 9 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ repositories {
mavenCentral()
}

sourceSets {
example {
java {
compileClasspath += main.output
runtimeClasspath += main.runtimeClasspath
}
}
}

dependencies {
compile 'com.eclipsesource.minimal-json:minimal-json:0.9.1'
compile 'org.bitbucket.b_c:jose4j:0.4.4'
Expand All @@ -26,6 +35,7 @@ dependencies {
testCompile 'org.mockito:mockito-core:1.9.5'
testCompile 'org.slf4j:slf4j-api:1.7.7'
testCompile 'org.slf4j:slf4j-nop:1.7.7'
exampleCompile 'com.eclipsesource.minimal-json:minimal-json:0.9.1'
}

compileJava {
Expand All @@ -52,15 +62,6 @@ javadoc {
checkstyleMain { classpath += configurations.compile }
checkstyleTest { classpath += configurations.compile }

sourceSets {
example {
java {
compileClasspath += main.output
runtimeClasspath += main.runtimeClasspath
}
}
}

task runExample(type: JavaExec, dependsOn: 'exampleClasses') {
classpath = sourceSets.example.runtimeClasspath
main = 'com.box.sdk.example.Main'
Expand All @@ -76,6 +77,11 @@ task runAccessAsAppUser(type: JavaExec, dependsOn: 'exampleClasses') {
main = 'com.box.sdk.example.AccessAsAppUser'
}

task runBatchExample(type: JavaExec, dependsOn: 'exampleClasses') {
classpath = sourceSets.example.runtimeClasspath
main = 'com.box.sdk.example.BatchRequestExample'
}

task javadocJar(type: Jar) {
classifier = 'javadoc'
from javadoc
Expand Down
4 changes: 4 additions & 0 deletions config/checkstyle/suppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@
files="example.*\.java"/>
<suppress checks="ImportOrder"
files="test.*\.java"/>
<suppress checks="DeclarationOrderCheck"
files="main/java/com/box/sdk/EventStream.java"/>
<suppress checks="DeclarationOrderCheck"
files="main/java/com/box/sdk/EventLog.java"/>
</suppressions>
87 changes: 87 additions & 0 deletions src/example/java/com/box/sdk/example/BatchRequestExample.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.box.sdk.example;

import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.box.sdk.BatchAPIRequest;
import com.box.sdk.BoxAPIRequest;
import com.box.sdk.BoxAPIResponse;
import com.box.sdk.BoxConfig;
import com.box.sdk.BoxDeveloperEditionAPIConnection;
import com.box.sdk.BoxFolder;
import com.box.sdk.BoxJSONRequest;
import com.box.sdk.BoxJSONResponse;
import com.box.sdk.BoxUser;
import com.box.sdk.IAccessTokenCache;
import com.box.sdk.InMemoryLRUAccessTokenCache;
import com.box.sdk.http.HttpMethod;

/**
*
*/
public final class BatchRequestExample {

private static final int MAX_CACHE_ENTRIES = 100;
private static final String APP_USER_NAME = "BATCH-TEST-APP-USER-NAME";

private BatchRequestExample() { }

public static void main(String[] args) throws IOException {
// Turn off logging to prevent polluting the output.
Logger.getLogger("com.box.sdk").setLevel(Level.OFF);


//It is a best practice to use an access token cache to prevent unneeded requests to Box for access tokens.
//For production applications it is recommended to use a distributed cache like Memcached or Redis, and to
//implement IAccessTokenCache to store and retrieve access tokens appropriately for your environment.
IAccessTokenCache accessTokenCache = new InMemoryLRUAccessTokenCache(MAX_CACHE_ENTRIES);

Reader reader = new FileReader("src/example/config/config.json");
BoxConfig boxConfig = BoxConfig.readFrom(reader);

BoxDeveloperEditionAPIConnection api = BoxDeveloperEditionAPIConnection.getAppEnterpriseConnection(
boxConfig, accessTokenCache);

List<BoxAPIRequest> requests = new ArrayList<BoxAPIRequest>();

//Get current user request
URL getMeURL = BoxUser.GET_ME_URL.build(api.getBaseURL());
BoxAPIRequest getMeRequest = new BoxAPIRequest(getMeURL, HttpMethod.GET);
requests.add(getMeRequest);

//Create App User Request
URL createUserURL = BoxUser.USERS_URL_TEMPLATE.build(api.getBaseURL());
BoxJSONRequest createAppUserRequest = new BoxJSONRequest(createUserURL, HttpMethod.POST);
createAppUserRequest.setBody("{\"name\":\"" + APP_USER_NAME + "\",\"is_platform_access_only\":true}");
requests.add(createAppUserRequest);

//Get Root Folder Request
URL getRootFolderURL = BoxFolder.FOLDER_INFO_URL_TEMPLATE.build(api.getBaseURL(), 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what will happen if more than 20 request?
What about the non-whitelisted request?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API will throw an error. No special handling in the SDK

BoxAPIRequest getRootFolderRequest = new BoxAPIRequest(getRootFolderURL, HttpMethod.GET);
requests.add(getRootFolderRequest);

BatchAPIRequest batchRequest = new BatchAPIRequest(api);
List<BoxAPIResponse> responses = batchRequest.execute(requests);

System.out.println("GET ME RESPONSE:");
System.out.println("Response Code: " + responses.get(0).getResponseCode());
System.out.println("Response: " + ((BoxJSONResponse) responses.get(1)).getJSON());

System.out.println("CREATE APP USER RESPONSE:");
System.out.println("Response Code: " + responses.get(1).getResponseCode());
System.out.println("Response: " + ((BoxJSONResponse) responses.get(1)).getJSON());

System.out.println("GET ROOT FOLDER RESPONSE:");
System.out.println("Response Code: " + responses.get(2).getResponseCode());
BoxJSONResponse rootFolderResponse = (BoxJSONResponse) responses.get(1);
BoxFolder.Info rootFolderInfo = new BoxFolder(api, rootFolderResponse.getJsonObject().get("id").asString())
.new Info(rootFolderResponse.getJsonObject());
System.out.println("Root Folder Created At: " + rootFolderInfo.getCreatedAt());
}
}
126 changes: 126 additions & 0 deletions src/main/java/com/box/sdk/BatchAPIRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.box.sdk;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.box.sdk.http.HttpHeaders;
import com.box.sdk.http.HttpMethod;
import com.eclipsesource.json.JsonArray;
import com.eclipsesource.json.JsonObject;
import com.eclipsesource.json.JsonValue;

/**
* Used to make a bunch of HTTP Requests as a batch. Currently the number of requests that can be batched at once
* is capped at <b>20</b> by the API layer. Also there are certain requests which <b>cannot</b> be performed
* by Batch API. Check API documentation for more information.
*
* <p>The request itself is a BoxJSONRequest but extends it to provide additional functionality for aggregating
* a bunch of requests and responding with multiple responses as if the requests were called individually</p>
*/
public class BatchAPIRequest extends BoxJSONRequest {
/**
* Batch URL Template.
*/
public static final URLTemplate BATCH_URL_TEMPLATE = new URLTemplate("batch");
private final BoxAPIConnection api;

/**
* Constructs an authenticated BatchRequest using a provided BoxAPIConnection.
* @param api an API connection for authenticating the request.
*/
public BatchAPIRequest(BoxAPIConnection api) {
super(api, BATCH_URL_TEMPLATE.build(api.getBaseURL()), HttpMethod.GET);
this.api = api;
}

/**
* Execute a set of API calls as batch request.
* @param requests list of api requests that has to be executed in batch.
* @return list of BoxAPIResponses
*/
public List<BoxAPIResponse> execute(List<BoxAPIRequest> requests) {
this.prepareRequest(requests);
BoxJSONResponse batchResponse = (BoxJSONResponse) send();
return this.parseResponse(batchResponse);
}

/**
* Prepare a batch api request using list of individual reuests.
* @param requests list of api requests that has to be executed in batch.
*/
protected void prepareRequest(List<BoxAPIRequest> requests) {
JsonObject body = new JsonObject();
JsonArray requestsJSONArray = new JsonArray();
for (BoxAPIRequest request: requests) {
JsonObject batchRequest = new JsonObject();
batchRequest.add("method", request.getMethod());
batchRequest.add("relative_url", request.getUrl().toString().substring(this.api.getBaseURL().length() - 1));
//If the actual request has a JSON body then add it to vatch request
if (request instanceof BoxJSONRequest) {
BoxJSONRequest jsonRequest = (BoxJSONRequest) request;
batchRequest.add("body", jsonRequest.getBodyAsJsonObject());
}
//Add any headers that are in the request, except Authorization
if (request.getHeaders() != null) {
JsonObject batchRequestHeaders = new JsonObject();
for (RequestHeader header: request.getHeaders()) {
if (header.getKey() != null && !header.getKey().isEmpty()
&& HttpHeaders.AUTHORIZATION.equals(header.getKey())) {
batchRequestHeaders.add(header.getKey(), header.getValue());
}
}
batchRequest.add("headers", batchRequestHeaders);
}

//Add the request to array
requestsJSONArray.add(batchRequest);
}
//Add the requests array to body
body.add("requests", requestsJSONArray);
super.setBody(body);
}

/**
* Parses btch api response to create a list of BoxAPIResponse objects.
* @param batchResponse response of a batch api request
* @return list of BoxAPIResponses
*/
protected List<BoxAPIResponse> parseResponse(BoxJSONResponse batchResponse) {
JsonObject responseJSON = JsonObject.readFrom(batchResponse.getJSON());
List<BoxAPIResponse> responses = new ArrayList<BoxAPIResponse>();
Iterator<JsonValue> responseIterator = responseJSON.get("responses").asArray().iterator();
while (responseIterator.hasNext()) {
JsonObject jsonResponse = responseIterator.next().asObject();
BoxAPIResponse response = null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible the batch API will throw 500?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally. That will be thrown as BoxAPIException


//Gather headers
Map<String, String> responseHeaders = new HashMap<String, String>();

if (jsonResponse.get("headers") != null) {
JsonObject batchResponseHeadersObject = jsonResponse.get("headers").asObject();
for (JsonObject.Member member : batchResponseHeadersObject) {
String headerName = member.getName();
String headerValue = member.getValue().asString();
responseHeaders.put(headerName, headerValue);
}
}

// Construct a BoxAPIResponse when response is null, or a BoxJSONResponse when there's a response
// (not anticipating any other response as per current APIs.
// Ideally we should do it based on response header)
if (jsonResponse.get("response") == null) {
response =
new BoxAPIResponse(jsonResponse.get("status").asInt(), responseHeaders);
} else {
response =
new BoxJSONResponse(jsonResponse.get("status").asInt(), responseHeaders,
jsonResponse.get("response").asObject());
}
responses.add(response);
}
return responses;
}
}
53 changes: 48 additions & 5 deletions src/main/java/com/box/sdk/BoxAPIRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;

import com.box.sdk.http.HttpHeaders;
import com.box.sdk.http.HttpMethod;

/**
Expand Down Expand Up @@ -81,11 +82,20 @@ public BoxAPIRequest(BoxAPIConnection api, URL url, String method) {
/**
* Constructs an authenticated BoxAPIRequest using a provided BoxAPIConnection.
* @param api an API connection for authenticating the request.
* @param uploadPartEndpoint the URL of the request.
* @param url the URL of the request.
* @param method the HTTP method of the request.
*/
public BoxAPIRequest(BoxAPIConnection api, URL uploadPartEndpoint, HttpMethod method) {
this(api, uploadPartEndpoint, method.name());
public BoxAPIRequest(BoxAPIConnection api, URL url, HttpMethod method) {
this(api, url, method.name());
}

/**
* Constructs an request, using URL and HttpMethod.
* @param url the URL of the request.
* @param method the HTTP method of the request.
*/
public BoxAPIRequest(URL url, HttpMethod method) {
this(url, method.name());
}

/**
Expand Down Expand Up @@ -182,6 +192,23 @@ public URL getUrl() {
return this.url;
}

/**
* Gets the http method from the request.
*
* @return http method
*/
public String getMethod() {
return this.method;
}

/**
* Get headers as list of RequestHeader objects.
* @return headers as list of RequestHeader objects
*/
protected List<RequestHeader> getHeaders() {
return this.headers;
}

/**
* Sends this request and returns a BoxAPIResponse containing the server's response.
*
Expand Down Expand Up @@ -377,7 +404,7 @@ private BoxAPIResponse trySend(ProgressListener listener) {

if (this.api != null) {
if (this.shouldAuthenticate) {
connection.addRequestProperty("Authorization", "Bearer " + this.api.lockAccessToken());
connection.addRequestProperty(HttpHeaders.AUTHORIZATION, "Bearer " + this.api.lockAccessToken());
}
connection.setRequestProperty("User-Agent", this.api.getUserAgent());
if (this.api.getProxy() != null) {
Expand Down Expand Up @@ -533,19 +560,35 @@ private static boolean isResponseRedirect(int responseCode) {
return (responseCode == 301 || responseCode == 302);
}

private final class RequestHeader {
/**
* Class for mapping a request header and value.
*/
public final class RequestHeader {
private final String key;
private final String value;

/**
* Construct a request header from header key and value.
* @param key header name
* @param value header value
*/
public RequestHeader(String key, String value) {
this.key = key;
this.value = value;
}

/**
* Get header key.
* @return http header name
*/
public String getKey() {
return this.key;
}

/**
* Get header value.
* @return http header value
*/
public String getValue() {
return this.value;
}
Expand Down
Loading