From a954994935094b82a4f4b9752da030a5d35cdb7b Mon Sep 17 00:00:00 2001 From: Dor Fire Date: Wed, 19 Oct 2016 10:25:18 +0300 Subject: [PATCH 001/119] Fixed #325 --- src/main/java/com/box/sdk/BoxSharedLink.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxSharedLink.java b/src/main/java/com/box/sdk/BoxSharedLink.java index c031eca53..4a3075a3b 100644 --- a/src/main/java/com/box/sdk/BoxSharedLink.java +++ b/src/main/java/com/box/sdk/BoxSharedLink.java @@ -18,6 +18,7 @@ public class BoxSharedLink extends BoxJSONObject { private long downloadCount; private long previewCount; private Access access; + private Access effectiveAccess; private Permissions permissions; /** @@ -150,6 +151,11 @@ public void setPermissions(Permissions permissions) { this.addChildObject("permissions", permissions); } + private Access parseAccessValue(JsonValue value) { + String accessString = value.asString().toUpperCase(); + return Access.valueOf(accessString); + } + @Override void parseJSONMember(JsonObject.Member member) { JsonValue value = member.getValue(); @@ -170,8 +176,9 @@ void parseJSONMember(JsonObject.Member member) { } else if (memberName.equals("preview_count")) { this.previewCount = Double.valueOf(value.toString()).longValue(); } else if (memberName.equals("access")) { - String accessString = value.asString().toUpperCase(); - this.access = Access.valueOf(accessString); + this.access = parseAccessValue(value); + } else if (memberName.equals("effective_access")) { + this.effectiveAccess = parseAccessValue(value); } else if (memberName.equals("permissions")) { if (this.permissions == null) { this.setPermissions(new Permissions(value.asObject())); From 1d13ec6e544f3f3666bd5904de0339773f76d394 Mon Sep 17 00:00:00 2001 From: Dor Fire Date: Wed, 19 Oct 2016 10:34:43 +0300 Subject: [PATCH 002/119] Fixed CheckStyle errors --- src/main/java/com/box/sdk/BoxSharedLink.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxSharedLink.java b/src/main/java/com/box/sdk/BoxSharedLink.java index 4a3075a3b..4c6b63894 100644 --- a/src/main/java/com/box/sdk/BoxSharedLink.java +++ b/src/main/java/com/box/sdk/BoxSharedLink.java @@ -176,9 +176,9 @@ void parseJSONMember(JsonObject.Member member) { } else if (memberName.equals("preview_count")) { this.previewCount = Double.valueOf(value.toString()).longValue(); } else if (memberName.equals("access")) { - this.access = parseAccessValue(value); + this.access = this.parseAccessValue(value); } else if (memberName.equals("effective_access")) { - this.effectiveAccess = parseAccessValue(value); + this.effectiveAccess = this.parseAccessValue(value); } else if (memberName.equals("permissions")) { if (this.permissions == null) { this.setPermissions(new Permissions(value.asObject())); From 8a168599e070cd4cea20db56a63ccd44efc08bef Mon Sep 17 00:00:00 2001 From: Dor Fire Date: Wed, 19 Oct 2016 11:11:07 +0300 Subject: [PATCH 003/119] Added a getter + setter for the new property --- src/main/java/com/box/sdk/BoxSharedLink.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/box/sdk/BoxSharedLink.java b/src/main/java/com/box/sdk/BoxSharedLink.java index 4c6b63894..437efd23b 100644 --- a/src/main/java/com/box/sdk/BoxSharedLink.java +++ b/src/main/java/com/box/sdk/BoxSharedLink.java @@ -122,13 +122,30 @@ public Access getAccess() { /** * Sets the access level of this shared link. - * @param access the new acccess level of this shared link. + * @param access the new access level of this shared link. */ public void setAccess(Access access) { this.access = access; this.addPendingChange("access", access.toJSONValue()); } + /** + * Gets the effective access level of this shared link. + * @return the effective access level of this shared link. + */ + public Access getEffectiveAccess() { + return this.effectiveAccess; + } + + /** + * Sets the effective access level of this shared link. + * @param access the new effective access level of this shared link. + */ + public void setEffectiveAccess(Access access) { + this.effectiveAccess = access; + this.addPendingChange("access", access.toJSONValue()); + } + /** * Gets the permissions associated with this shared link. * @return the permissions associated with this shared link. From a5ca6b091cd61edeace23bbf62f506ab2d7a47ee Mon Sep 17 00:00:00 2001 From: Dor Fire Date: Wed, 19 Oct 2016 11:32:15 +0300 Subject: [PATCH 004/119] Added a verification test --- src/test/java/com/box/sdk/BoxFolderTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/test/java/com/box/sdk/BoxFolderTest.java b/src/test/java/com/box/sdk/BoxFolderTest.java index 51ea9ec16..9e4697ef5 100644 --- a/src/test/java/com/box/sdk/BoxFolderTest.java +++ b/src/test/java/com/box/sdk/BoxFolderTest.java @@ -554,4 +554,21 @@ public void createWebLinkNoNameOrDescriptionSucceeds() throws MalformedURLExcept createdWebLink.delete(); assertThat(rootFolder, not(hasItem(Matchers.hasProperty("ID", equalTo(createdWebLink.getID()))))); } + + /** + * Verifies the fix for issue #325 + */ + @Test + @Category(IntegrationTest.class) + public void sharedLinkInfoHasEffectiveAccess() { + BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); + BoxFolder rootFolder = BoxFolder.getRootFolder(api); + BoxFolder folder = rootFolder.createFolder("[sharedLinkInfoHasEffectiveAccess] Test Folder").getResource(); + BoxSharedLink sharedLink = folder.createSharedLink(BoxSharedLink.Access.OPEN, null, null); + + assertThat(sharedLink, Matchers.hasProperty("effectiveAccess")); + assertThat(sharedLink.getEffectiveAccess(), equalTo(BoxSharedLink.Access.OPEN)); + + folder.delete(true); + } } From 3bdab34d1e2a994d07660e74352b17175f387567 Mon Sep 17 00:00:00 2001 From: Dor Fire Date: Thu, 20 Oct 2016 10:03:58 +0300 Subject: [PATCH 005/119] Added method BoxMultipartRequest.setContentSHA1 --- src/main/java/com/box/sdk/BoxMultipartRequest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/com/box/sdk/BoxMultipartRequest.java b/src/main/java/com/box/sdk/BoxMultipartRequest.java index e04ad9a95..fa5cdae0c 100644 --- a/src/main/java/com/box/sdk/BoxMultipartRequest.java +++ b/src/main/java/com/box/sdk/BoxMultipartRequest.java @@ -87,6 +87,15 @@ public void setFile(InputStream inputStream, String filename, long fileSize) { this.fileSize = fileSize; } + /** + * Sets the SHA1 hash of the file contents of this request. + * If set, it will ensure that the file is not corrupted in transit. + * @param sha1 a string containing the SHA1 hash of the file contents. + */ + public void setContentSHA1(String sha1) { + this.addHeader("Content-MD5", sha1); + } + /** * This method is unsupported in BoxMultipartRequest. Instead, the body should be modified via the {@code putField} * and {@code setFile} methods. From e64091ada36b724173ff600a5d64e27632785068 Mon Sep 17 00:00:00 2001 From: Dor Fire Date: Thu, 20 Oct 2016 10:29:16 +0300 Subject: [PATCH 006/119] Added SHA1 parameter to BoxFile.uploadVersion variants --- src/main/java/com/box/sdk/BoxFile.java | 48 +++++++++++++++++++++----- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index e28409185..986f16bef 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -447,31 +447,63 @@ public void uploadVersion(InputStream fileContent) { /** * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts * will be able to view and recover previous versions of the file. - * @param fileContent a stream containing the new file contents. - * @param modified the date that the new version was modified. + * @param fileContent a stream containing the new file contents. + * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents. + * + */ + public void uploadVersion(InputStream fileContent, String fileContentSHA1) { + this.uploadVersion(fileContent, null, null); + } + + /** + * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts + * will be able to view and recover previous versions of the file. + * @param fileContent a stream containing the new file contents. + * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents. + * @param modified the date that the new version was modified. */ - public void uploadVersion(InputStream fileContent, Date modified) { - this.uploadVersion(fileContent, modified, 0, null); + public void uploadVersion(InputStream fileContent, String fileContentSHA1, Date modified) { + this.uploadVersion(fileContent, fileContentSHA1, modified, 0, null); } /** * Uploads a new version of this file, replacing the current version, while reporting the progress to a * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions * of the file. - * @param fileContent a stream containing the new file contents. - * @param modified the date that the new version was modified. - * @param fileSize the size of the file used for determining the progress of the upload. - * @param listener a listener for monitoring the upload's progress. + * @param fileContent a stream containing the new file contents. + * @param modified the date that the new version was modified. + * @param fileSize the size of the file used for determining the progress of the upload. + * @param listener a listener for monitoring the upload's progress. */ public void uploadVersion(InputStream fileContent, Date modified, long fileSize, ProgressListener listener) { + this.uploadVersion(fileContent, null, modified, fileSize, listener); + } + + /** + * Uploads a new version of this file, replacing the current version, while reporting the progress to a + * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions + * of the file. + * @param fileContent a stream containing the new file contents. + * @param fileContentSHA1 the SHA1 hash of the file contents. will be sent along in the Content-MD5 header + * @param modified the date that the new version was modified. + * @param fileSize the size of the file used for determining the progress of the upload. + * @param listener a listener for monitoring the upload's progress. + */ + public void uploadVersion(InputStream fileContent, String fileContentSHA1, Date modified, long fileSize, + ProgressListener listener) { URL uploadURL = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID()); BoxMultipartRequest request = new BoxMultipartRequest(getAPI(), uploadURL); + if (fileSize > 0) { request.setFile(fileContent, "", fileSize); } else { request.setFile(fileContent, ""); } + if (fileContentSHA1 != null) { + request.setContentSHA1(fileContentSHA1); + } + if (modified != null) { request.putField("content_modified_at", modified); } From bd58270ad3a785d5786606737d99467072971117 Mon Sep 17 00:00:00 2001 From: gleb-urvanov Date: Tue, 25 Oct 2016 14:13:54 +0200 Subject: [PATCH 007/119] paging and fields support implementation, tests and javadocs for groups and memberships --- doc/groups.md | 94 ++++- src/main/java/com/box/sdk/BoxGroup.java | 101 ++++++ .../java/com/box/sdk/BoxGroupMembership.java | 51 +++ .../box/sdk/BoxGroupMembershipIterator.java | 35 +- src/main/java/com/box/sdk/BoxUser.java | 20 ++ .../com/box/sdk/BoxGroupMembershipTest.java | 105 +++++- src/test/java/com/box/sdk/BoxGroupTest.java | 335 +++++++++++++++++- src/test/java/com/box/sdk/BoxUserTest.java | 119 +++++++ 8 files changed, 851 insertions(+), 9 deletions(-) diff --git a/doc/groups.md b/doc/groups.md index b99f85bf3..9ae5857aa 100644 --- a/doc/groups.md +++ b/doc/groups.md @@ -7,6 +7,12 @@ Groups are sets of users that can be used in collaborations. * [Create a Group](#create-a-group) * [Delete a Group](#delete-a-group) * [Get a Groups collaborations](#get-a-groups-collaborations) +* [Create Membership](#create-membership) +* [Get Membership](#get-membership) +* [Update Membership](#update-membership) +* [Delete Membership](#delete-membership) +* [Get Memberships for Group](#get-memberships-for-group) +* [Get Memberships for User](#get-memberships-for-user) Get All Groups -------------- @@ -50,11 +56,95 @@ group.delete(); Get a Groups collaborations --------------------------- -A groups collaborations can be retrieved by calling the [`getCollaborations()`[get-collaborations] method. +A groups collaborations can be retrieved by calling the [`getCollaborations()`][get-collaborations] method. ```java BoxGroup group = new BoxGroup(api, "id"); group.getCollaborations(); ``` -[get-collaborations]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxGroup.html#getCollaborations() \ No newline at end of file +[get-collaborations]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxGroup.html#getCollaborations() + +Create Membership +--------------- + +Membership for the group can be created by calling [`addMembership(BoxUser)`][add-membership] and [`addMembership(BoxUser, Role)`][add-membership2] methods. + +```java +BoxGroup group = new BoxGroup(api, "groupID"); +BoxUser user = new BoxUser(api, "userID"); +BoxGroupMembership.Info groupMembershipInfo = group.addMembership(user); +``` + +[add-membership]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxGroup.html#addMembership(com.box.sdk.BoxUser) +[add-membership2]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxGroup.html#addMembership(com.box.sdk.BoxUser,%20com.box.sdk.BoxGroupMembership.Role) + +Get Membership +--------------- + +A groups membership can be retrieved by calling the [`BoxGroupMembership.getInfo()`][get-membership] method. + +```java +BoxGroupMembership membership = new BoxGroupMembership(api, id); +BoxGroupMembership.Info groupMembershipInfo = membership.getInfo(); +``` + +[get-membership]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxGroupMembership.html#getInfo() + +Update Membership +--------------- + +A groups membership can be updated by calling the [`BoxGroupMembership.updateInfo(BoxGroupMembership.Info)`][update-membership] method. + +```java +BoxGroupMembership membership = new BoxGroupMembership(api, id); +BoxGroupMembership.Info info = membership.new Info(); +info.addPendingChange("role", role); +membership.updateInfo(info); +``` + +[update-membership]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxGroupMembership.html#updateInfo(com.box.sdk.BoxGroupMembership.Info) + +Delete Membership +--------------- + +A group can be deleted by calling the [`BoxGroupMembership.delete()`][delete-membership] method. + +```java +BoxGroupMembership membership = new BoxGroupMembership(api, id); +membership.delete(); +``` + +[delete-membership]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxGroupMembership.html#delete() + +Get Memberships for Group +--------------- + +Calling the [`getAllMemberships(String...)`][get-memberships-for-group] will return an iterable that will page through all of the group's memberships. +Optional parameters can be used to retrieve specific fields of the Group Membership object. + +```java +BoxGroup group = new BoxGroup(api, id); +Iterable memberships = group.getAllMemberships(); +for (BoxGroupMembership.Info membershipInfo : memberships) { + // Do something with the membership. +} +``` + +[get-memberships-for-group]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxGroup.html#getAllMemberships(java.lang.String...) + +Get Memberships for User +--------------- + +Calling the [`BoxUser.getAllMemberships(String...)`][get-memberships-for-user] will return an iterable that will page through all of the user's memberships. +Optional parameters can be used to retrieve specific fields of the Group Membership object. + +```java +BoxUser user = new BoxUser(api, id); +Iterable memberships = user.getAllMemberships(); +for (BoxGroupMembership.Info membershipInfo : memberships) { + // Do something with the membership. +} +``` + +[get-memberships-for-user]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxUser.html#getAllMemberships(java.lang.String...) \ No newline at end of file diff --git a/src/main/java/com/box/sdk/BoxGroup.java b/src/main/java/com/box/sdk/BoxGroup.java index 07f8e7cd9..2c447fd64 100644 --- a/src/main/java/com/box/sdk/BoxGroup.java +++ b/src/main/java/com/box/sdk/BoxGroup.java @@ -19,10 +19,30 @@ */ @BoxResourceType("group") public class BoxGroup extends BoxCollaborator { + + /** + * @see #getAllGroups(BoxAPIConnection, String...) + */ private static final URLTemplate GROUPS_URL_TEMPLATE = new URLTemplate("groups"); + + /** + * @see #getInfo() + */ private static final URLTemplate GROUP_URL_TEMPLATE = new URLTemplate("groups/%s"); + + /** + * @see #getMemberships() + */ private static final URLTemplate MEMBERSHIPS_URL_TEMPLATE = new URLTemplate("groups/%s/memberships"); + + /** + * @see #addMembership(BoxUser) + */ private static final URLTemplate ADD_MEMBERSHIP_URL_TEMPLATE = new URLTemplate("group_memberships"); + + /** + * @see #getCollaborations() + */ private static final URLTemplate COLLABORATIONS_URL_TEMPLATE = new URLTemplate("groups/%s/collaborations"); /** @@ -101,6 +121,25 @@ public Iterator iterator() { }; } + /** + * Gets an iterable of all the groups in the enterprise. + * @param api the API connection to be used when retrieving the groups. + * @param fields the fields to retrieve. + * @return an iterable containing info about all the groups. + */ + public static Iterable getAllGroups(final BoxAPIConnection api, String ... fields) { + final QueryStringBuilder builder = new QueryStringBuilder(); + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + return new Iterable() { + public Iterator iterator() { + URL url = GROUPS_URL_TEMPLATE.buildWithQuery(api.getBaseURL(), builder.toString()); + return new BoxGroupIterator(api, url); + } + }; + } + /** * Gets information about this group. * @return info about this group. @@ -113,8 +152,26 @@ public Info getInfo() { return new Info(responseJSON); } + /** + * Gets information about this group. + * @param fields the fields to retrieve. + * @return info about this group. + */ + public Info getInfo(String ... fields) { + QueryStringBuilder builder = new QueryStringBuilder(); + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + URL url = GROUP_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), builder.toString(), this.getID()); + BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); + return new Info(responseJSON); + } + /** * Gets information about all of the group memberships for this group. + * Does not support paging. * @return a collection of information about the group memberships for this group. */ public Collection getMemberships() { @@ -137,6 +194,25 @@ public Iterator iterator() { return memberships; } + /** + * Gets information about all of the group memberships for this group as iterable with paging support. + * @param fields the fields to retrieve. + * @return an iterable with information about the group memberships for this group. + */ + public Iterable getAllMemberships(String ... fields) { + final QueryStringBuilder builder = new QueryStringBuilder(); + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + return new Iterable() { + public Iterator iterator() { + URL url = MEMBERSHIPS_URL_TEMPLATE.buildWithQuery( + BoxGroup.this.getAPI().getBaseURL(), builder.toString(), BoxGroup.this.getID()); + return new BoxGroupMembershipIterator(BoxGroup.this.getAPI(), url); + } + }; + } + /** * Adds a member to this group with the default role. * @param user the member to be added to this group. @@ -212,10 +288,29 @@ public void delete() { */ public class Info extends BoxCollaborator.Info { + /** + * @see #getProvenance() + */ private String provenance; + + /** + * @see #getExternalSyncIdentifier() + */ private String externalSyncIdentifier; + + /** + * @see #getDescription() + */ private String description; + + /** + * @see #getInvitabilityLevel() + */ private String invitabilityLevel; + + /** + * @see #getMemberViewabilityLevel() + */ private String memberViewabilityLevel; /** @@ -241,11 +336,17 @@ public Info(String json) { super(jsonObject); } + /** + * {@inheritDoc} + */ @Override public BoxGroup getResource() { return BoxGroup.this; } + /** + * {@inheritDoc} + */ @Override protected void parseJSONMember(JsonObject.Member member) { super.parseJSONMember(member); diff --git a/src/main/java/com/box/sdk/BoxGroupMembership.java b/src/main/java/com/box/sdk/BoxGroupMembership.java index 2439a3d00..8d4781867 100644 --- a/src/main/java/com/box/sdk/BoxGroupMembership.java +++ b/src/main/java/com/box/sdk/BoxGroupMembership.java @@ -16,6 +16,11 @@ */ @BoxResourceType("group_membership") public class BoxGroupMembership extends BoxResource { + + /** + * The URL template for all group membership requests. + * @see #getInfo() + */ private static final URLTemplate MEMBERSHIP_URL_TEMPLATE = new URLTemplate("group_memberships/%s"); /** @@ -72,10 +77,30 @@ public void delete() { * Contains information about a BoxGroupMembership. */ public class Info extends BoxResource.Info { + + /** + * @see #getUser() + */ private BoxUser.Info user; + + /** + * @see #getGroup() + */ private BoxGroup.Info group; + + /** + * @see #getRole() + */ private Role role; + + /** + * @see #getCreatedAt() + */ private Date createdAt; + + /** + * @see #getModifiedAt() + */ private Date modifiedAt; /** @@ -157,11 +182,17 @@ public Date getModifiedAt() { return this.modifiedAt; } + /** + * {@inheritDoc} + */ @Override public BoxGroupMembership getResource() { return BoxGroupMembership.this; } + /** + * {@inheritDoc} + */ @Override protected void parseJSONMember(JsonObject.Member member) { super.parseJSONMember(member); @@ -215,21 +246,41 @@ public enum Role { */ ADMIN ("admin"), + /** + * The user is a submaster in the group. + */ + SUBMASTER ("submaster"), + /** * The user is a regular member in the group. */ MEMBER ("member"); + /** + * String representation of the role. + */ private final String jsonValue; + /** + * Constructor. + * @param jsonValue srting representation of the role. + */ private Role(String jsonValue) { this.jsonValue = jsonValue; } + /** + * Creates the role from given string. + * @param jsonValue string to be converted to role. + * @return the role, created from string value. + */ static Role fromJSONString(String jsonValue) { return Role.valueOf(jsonValue.toUpperCase()); } + /** + * @return string representation of the role. + */ String toJSONString() { return this.jsonValue; } diff --git a/src/main/java/com/box/sdk/BoxGroupMembershipIterator.java b/src/main/java/com/box/sdk/BoxGroupMembershipIterator.java index 8fe077978..998b1f185 100644 --- a/src/main/java/com/box/sdk/BoxGroupMembershipIterator.java +++ b/src/main/java/com/box/sdk/BoxGroupMembershipIterator.java @@ -5,29 +5,58 @@ import com.eclipsesource.json.JsonObject; +/** + * An iterator object for {@link BoxGroupMembership} object. + * Supports offset-based paging. + */ class BoxGroupMembershipIterator implements Iterator { - private static final long LIMIT = 1000; + /** + * The limit of entries per response. + */ + private static final long LIMIT = 100; + + /** + * The API connection to be used. + */ private final BoxAPIConnection api; + + /** + * Iterator object with paging support. + */ private final JSONIterator jsonIterator; + /** + * Creates new BoxGroupMembership iterator. + * @param api The API connection to be used by the iterator. + * @param url The endpoint url. + */ BoxGroupMembershipIterator(BoxAPIConnection api, URL url) { this.api = api; this.jsonIterator = new JSONIterator(api, url, LIMIT); } + /** + * @return false if current element is the last. + */ public boolean hasNext() { return this.jsonIterator.hasNext(); } + /** + * @return next BoxGroupMembeship object in the list. + */ public BoxGroupMembership.Info next() { JsonObject nextJSONObject = this.jsonIterator.next(); String id = nextJSONObject.get("id").asString(); - BoxGroupMembership group = new BoxGroupMembership(this.api, id); - return group.new Info(nextJSONObject); + BoxGroupMembership membership = new BoxGroupMembership(this.api, id); + return membership.new Info(nextJSONObject); } + /** + * Remove operation is not supported. + */ public void remove() { throw new UnsupportedOperationException(); } diff --git a/src/main/java/com/box/sdk/BoxUser.java b/src/main/java/com/box/sdk/BoxUser.java index 369b49a16..c5fa0f64d 100644 --- a/src/main/java/com/box/sdk/BoxUser.java +++ b/src/main/java/com/box/sdk/BoxUser.java @@ -247,6 +247,7 @@ public BoxUser.Info getInfo(String... fields) { /** * Gets information about all of the group memberships for this user. + * Does not support paging. * *

Note: This method is only available to enterprise admins.

* @@ -273,6 +274,25 @@ public Collection getMemberships() { return memberships; } + /** + * Gets information about all of the group memberships for this user as iterable with paging support. + * @param fields the fields to retrieve. + * @return an iterable with information about the group memberships for this user. + */ + public Iterable getAllMemberships(String ... fields) { + final QueryStringBuilder builder = new QueryStringBuilder(); + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + return new Iterable() { + public Iterator iterator() { + URL url = USER_MEMBERSHIPS_URL_TEMPLATE.buildWithQuery( + BoxUser.this.getAPI().getBaseURL(), builder.toString(), BoxUser.this.getID()); + return new BoxGroupMembershipIterator(BoxUser.this.getAPI(), url); + } + }; + } + /** * Adds a new email alias to this user's account. * @param email the email address to add as an alias. diff --git a/src/test/java/com/box/sdk/BoxGroupMembershipTest.java b/src/test/java/com/box/sdk/BoxGroupMembershipTest.java index f6886d42c..f55ca10c9 100644 --- a/src/test/java/com/box/sdk/BoxGroupMembershipTest.java +++ b/src/test/java/com/box/sdk/BoxGroupMembershipTest.java @@ -1,26 +1,45 @@ package com.box.sdk; import java.text.ParseException; +import java.util.Date; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; +import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; -import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.delete; +import static com.github.tomakehurst.wiremock.client.WireMock.deleteRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; import com.box.sdk.BoxGroupMembership.Role; import com.eclipsesource.json.JsonObject; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.junit.WireMockRule; +/** + * {@link BoxGroupMembership} related tests. + */ public class BoxGroupMembershipTest { + + /** + * Wiremock + */ @Rule public WireMockRule wireMockRule = new WireMockRule(8080); + /** + * Unit test for {@link BoxGroupMembership#getInfo()}. + */ @Test @Category(UnitTest.class) public void getInfoSendsCorrectRequestAndParsesResponseCorrectly() throws ParseException { @@ -78,6 +97,9 @@ public void getInfoSendsCorrectRequestAndParsesResponseCorrectly() throws ParseE .withRequestBody(WireMock.equalTo(""))); } + /** + * Unit test for {@link BoxGroupMembership#delete()}. + */ @Test @Category(UnitTest.class) public void deleteMembershipSendsCorrectRequest() { @@ -98,6 +120,87 @@ public void deleteMembershipSendsCorrectRequest() { .withRequestBody(WireMock.equalTo(""))); } + /** + * Unit test for {@link BoxGroupMembership#updateInfo(BoxGroupMembership.Info)}. + */ + @Test + @Category(UnitTest.class) + public void testUpdateInfoSendsCorrectJson() { + final String role = "submaster"; + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals("https://api.box.com/2.0/group_memberships/0", request.getUrl().toString()); + Assert.assertEquals(role, json.get("role").asString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{}"; + } + }; + } + }); + + BoxGroupMembership membership = new BoxGroupMembership(api, "0"); + BoxGroupMembership.Info info = membership.new Info(); + info.addPendingChange("role", role); + membership.updateInfo(info); + } + + /** + * Unit test for {@link BoxGroupMembership#updateInfo(BoxGroupMembership.Info)}. + */ + @Test + @Category(UnitTest.class) + public void testUpdateInfoParseAllFieldsCorrectly() throws ParseException { + final String id = "1560354"; + final String userID = "13130406"; + final String userName = "Alison Wonderland"; + final String userLogin = "alice@gmail.com"; + final String groupID = "119720"; + final String groupName = "family"; + final Role role = Role.SUBMASTER; + final Date createdAt = BoxDateFormat.parse("2013-05-16T15:27:57-07:00"); + final Date modifiedAt = BoxDateFormat.parse("2013-05-16T15:27:57-07:00"); + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"group_membership\",\n" + + " \"id\": \"1560354\",\n" + + " \"user\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"13130406\",\n" + + " \"name\": \"Alison Wonderland\",\n" + + " \"login\": \"alice@gmail.com\"\n" + + " },\n" + + " \"group\": {\n" + + " \"type\": \"group\",\n" + + " \"id\": \"119720\",\n" + + " \"name\": \"family\"\n" + + " },\n" + + " \"role\": \"submaster\",\n" + + " \"created_at\": \"2013-05-16T15:27:57-07:00\",\n" + + " \"modified_at\": \"2013-05-16T15:27:57-07:00\"\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + BoxGroupMembership membership = new BoxGroupMembership(api, id); + BoxGroupMembership.Info info = membership.new Info(); + info.addPendingChange("role", "non-empty"); + membership.updateInfo(info); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(userID, info.getUser().getID()); + Assert.assertEquals(userName, info.getUser().getName()); + Assert.assertEquals(userLogin, info.getUser().getLogin()); + Assert.assertEquals(groupID, info.getGroup().getID()); + Assert.assertEquals(groupName, info.getGroup().getName()); + Assert.assertEquals(role, info.getRole()); + Assert.assertEquals(createdAt, info.getCreatedAt()); + Assert.assertEquals(modifiedAt, info.getModifiedAt()); + } + @Test @Category(IntegrationTest.class) public void getInfoSucceeds() { diff --git a/src/test/java/com/box/sdk/BoxGroupTest.java b/src/test/java/com/box/sdk/BoxGroupTest.java index c74fea72d..4860dc351 100644 --- a/src/test/java/com/box/sdk/BoxGroupTest.java +++ b/src/test/java/com/box/sdk/BoxGroupTest.java @@ -2,29 +2,352 @@ import java.text.ParseException; import java.util.Collection; +import java.util.Date; +import java.util.Iterator; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; -import static org.skyscreamer.jsonassert.JSONCompareMode.*; +import static org.skyscreamer.jsonassert.JSONCompareMode.LENIENT; import org.hamcrest.Matchers; +import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; -import static com.github.tomakehurst.wiremock.client.WireMock.*; - +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.delete; +import static com.github.tomakehurst.wiremock.client.WireMock.deleteRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; + +import com.eclipsesource.json.JsonArray; import com.eclipsesource.json.JsonObject; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.junit.WireMockRule; +/** + * {@link BoxGroup} related tests. + */ public class BoxGroupTest { + + /** + * Wiremock + */ @Rule public WireMockRule wireMockRule = new WireMockRule(8080); + /** + * Unit test for {@link BoxGroup#getInfo(String...)}. + */ + @Test + @Category(UnitTest.class) + public void testGetInfoSendsCorrectRequestWithParams() { + final JsonObject fakeJSONResponse = new JsonObject() + .add("id", "0"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals( + "https://api.box.com/2.0/groups/0?fields=name%2Ccreated_at", request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return fakeJSONResponse.toString(); + } + }; + } + }); + + BoxGroup group = new BoxGroup(api, "0"); + group.getInfo("name", "created_at"); + } + + /** + * Unit test for {@link BoxGroup#getInfo()}. + */ + @Test + @Category(UnitTest.class) + public void testGetInfoSendsCorrectRequest() { + final JsonObject fakeJSONResponse = new JsonObject() + .add("id", "0"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals( + "https://api.box.com/2.0/groups/0", request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return fakeJSONResponse.toString(); + } + }; + } + }); + + BoxGroup group = new BoxGroup(api, "0"); + group.getInfo(); + } + + /** + * Unit test for {@link BoxGroup#getInfo()}. + */ + @Test + @Category(UnitTest.class) + public void testGetInfoParseAllFieldsCorrectly() throws ParseException { + final String id = "119720"; + final String name = "Box Employees"; + final Date createdAt = BoxDateFormat.parse("2013-05-16T15:27:57-07:00"); + final Date modifiedAt = BoxDateFormat.parse("2013-05-16T15:27:57-07:00"); + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"group\",\n" + + " \"id\": \"119720\",\n" + + " \"name\": \"Box Employees\",\n" + + " \"created_at\": \"2013-05-16T15:27:57-07:00\",\n" + + " \"modified_at\": \"2013-05-16T15:27:57-07:00\"\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxGroup group = new BoxGroup(api, id); + BoxGroup.Info info = group.getInfo(); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(name, info.getName()); + Assert.assertEquals(createdAt, info.getCreatedAt()); + Assert.assertEquals(modifiedAt, info.getModifiedAt()); + } + + /** + * Unit test for {@link BoxGroup#addMembership(BoxUser)}. + */ + @Test + @Category(UnitTest.class) + public void testAddMembershipSendsCorrectJson() { + final String userID = "1992432"; + final String groupID = "1992433"; + + final JsonObject fakeJSONResponse = new JsonObject().add("id", "0"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals("https://api.box.com/2.0/group_memberships", request.getUrl().toString()); + Assert.assertEquals(userID, json.get("user").asObject().get("id").asString()); + Assert.assertEquals(groupID, json.get("group").asObject().get("id").asString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return fakeJSONResponse.toString(); + } + }; + } + }); + + BoxGroup group = new BoxGroup(api, groupID); + group.addMembership(new BoxUser(api, userID)); + } + + /** + * Unit test for {@link BoxGroup#addMembership(BoxUser)}. + */ + @Test + @Category(UnitTest.class) + public void testAddMembershipParseAllFieldscorrectly() { + final String id = "1560354"; + final String userID = "13130406"; + final String userName = "Alison Wonderland"; + final String userLogin = "alice@gmail.com"; + final String groupID = "119720"; + final String groupName = "family"; + final BoxGroupMembership.Role role = BoxGroupMembership.Role.MEMBER; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"group_membership\",\n" + + " \"id\": \"1560354\",\n" + + " \"user\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"13130406\",\n" + + " \"name\": \"Alison Wonderland\",\n" + + " \"login\": \"alice@gmail.com\"\n" + + " },\n" + + " \"group\": {\n" + + " \"type\": \"group\",\n" + + " \"id\": \"119720\",\n" + + " \"name\": \"family\"\n" + + " },\n" + + " \"role\": \"member\"\n" + + " }"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + BoxGroup group = new BoxGroup(api, id); + BoxGroupMembership.Info info = group.addMembership(new BoxUser(api, "0")); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(userID, info.getUser().getID()); + Assert.assertEquals(userName, info.getUser().getName()); + Assert.assertEquals(userLogin, info.getUser().getLogin()); + Assert.assertEquals(groupID, info.getGroup().getID()); + Assert.assertEquals(groupName, info.getGroup().getName()); + Assert.assertEquals(role, info.getRole()); + } + + /** + * Unit test for {@link BoxGroup#getAllMemberships(String...)}. + */ + @Test + @Category(UnitTest.class) + public void testGetAllMembershipsParseAllFieldsCorrectly() { + final String firstMembershipID = "1560354"; + final String firstMembershipUserID = "13130906"; + final String firstMembershipUserName = "Alice"; + final String firstMembershipUserLogin = "alice@gmail.com"; + final String groupID = "119720"; + final String groupName = "family"; + final BoxGroupMembership.Role role = BoxGroupMembership.Role.MEMBER; + final String secondMembershipID = "1560356"; + final String secondMembershipUserID = "192633962"; + final String secondMembershipUserName = "rabbit"; + final String secondMembershipUserLogin = "rabbit@gmail.com"; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"total_count\": 2,\n" + + " \"entries\": [\n" + + " {\n" + + " \"type\": \"group_membership\",\n" + + " \"id\": \"1560354\",\n" + + " \"user\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"13130906\",\n" + + " \"name\": \"Alice\",\n" + + " \"login\": \"alice@gmail.com\"\n" + + " },\n" + + " \"group\": {\n" + + " \"type\": \"group\",\n" + + " \"id\": \"119720\",\n" + + " \"name\": \"family\"\n" + + " },\n" + + " \"role\": \"member\"\n" + + " },\n" + + " {\n" + + " \"type\": \"group_membership\",\n" + + " \"id\": \"1560356\",\n" + + " \"user\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"192633962\",\n" + + " \"name\": \"rabbit\",\n" + + " \"login\": \"rabbit@gmail.com\"\n" + + " },\n" + + " \"group\": {\n" + + " \"type\": \"group\",\n" + + " \"id\": \"119720\",\n" + + " \"name\": \"family\"\n" + + " },\n" + + " \"role\": \"member\"\n" + + " }\n" + + " ],\n" + + " \"offset\": 0,\n" + + " \"limit\": 100\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + BoxGroup group = new BoxGroup(api, "0"); + Iterator iterator = group.getAllMemberships().iterator(); + BoxGroupMembership.Info info = iterator.next(); + Assert.assertEquals(firstMembershipID, info.getID()); + Assert.assertEquals(firstMembershipUserID, info.getUser().getID()); + Assert.assertEquals(firstMembershipUserName, info.getUser().getName()); + Assert.assertEquals(firstMembershipUserLogin, info.getUser().getLogin()); + Assert.assertEquals(groupID, info.getGroup().getID()); + Assert.assertEquals(groupName, info.getGroup().getName()); + Assert.assertEquals(role, info.getRole()); + info = iterator.next(); + Assert.assertEquals(secondMembershipID, info.getID()); + Assert.assertEquals(secondMembershipUserID, info.getUser().getID()); + Assert.assertEquals(secondMembershipUserName, info.getUser().getName()); + Assert.assertEquals(secondMembershipUserLogin, info.getUser().getLogin()); + Assert.assertEquals(groupID, info.getGroup().getID()); + Assert.assertEquals(groupName, info.getGroup().getName()); + Assert.assertEquals(role, info.getRole()); + Assert.assertEquals(false, iterator.hasNext()); + } + + /** + * Unit test for {@link BoxGroup#getAllMemberships(String...)}. + */ + @Test + @Category(UnitTest.class) + public void testGetAllMembershipsSendsCorrectRequest() { + final JsonObject fakeJSONResponse = new JsonObject() + .add("total_count", 0) + .add("offset", 0) + .add("entries", new JsonArray()); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals( + "https://api.box.com/2.0/groups/0/memberships?fields=user%2Cgroup&limit=100&offset=0", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return fakeJSONResponse.toString(); + } + }; + } + }); + + BoxGroup group = new BoxGroup(api, "0"); + Iterator iterator = group.getAllMemberships("user", "group").iterator(); + iterator.hasNext(); + } + + /** + * Unit test for {@link BoxGroup#getAllGroups(BoxAPIConnection, String...)}. + */ + @Test + @Category(UnitTest.class) + public void testGetAllGroupsSendsCorrectRequest() { + final JsonObject fakeJSONResponse = new JsonObject() + .add("total_count", 0) + .add("offset", 0) + .add("entries", new JsonArray()); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/groups?fields=name%2Ccreated_at&limit=1000&offset=0", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return fakeJSONResponse.toString(); + } + }; + } + }); + + Iterator iterator = BoxGroup.getAllGroups(api, "name", "created_at").iterator(); + iterator.hasNext(); + } + @Test @Category(IntegrationTest.class) public void createAndDeleteGroupSucceeds() { @@ -74,6 +397,9 @@ public void getInfoSucceeds() { createdGroupInfo.getResource().delete(); } + /** + * Unit test for {@link BoxGroup#createGroup(BoxAPIConnection, String, String, String, String, String, String)}. + */ @Test @Category(UnitTest.class) public void createGroupSendsCorrectRequestAndParsesResponseCorrectly() throws ParseException { @@ -131,6 +457,9 @@ public void createGroupSendsCorrectRequestAndParsesResponseCorrectly() throws Pa .withRequestBody(equalToJson(expectedJSON.toString(), LENIENT))); } + /** + * Unit test for {@link BoxGroup#delete()}. + */ @Test @Category(UnitTest.class) public void deleteGroupSendsCorrectRequest() { diff --git a/src/test/java/com/box/sdk/BoxUserTest.java b/src/test/java/com/box/sdk/BoxUserTest.java index 84cf83c1f..c30d0d188 100644 --- a/src/test/java/com/box/sdk/BoxUserTest.java +++ b/src/test/java/com/box/sdk/BoxUserTest.java @@ -3,6 +3,7 @@ import java.text.ParseException; import java.util.Collection; import java.util.Date; +import java.util.Iterator; import java.util.List; import static org.hamcrest.Matchers.equalTo; @@ -33,10 +34,20 @@ import com.github.tomakehurst.wiremock.junit.WireMockRule; import com.google.common.collect.Lists; +/** + * {@link BoxUser} related tests. + */ public class BoxUserTest { + + /** + * Wiremock + */ @Rule public final WireMockRule wireMockRule = new WireMockRule(8080); + /** + * Unit test for {@link BoxUser#getAllEnterpriseUsers(BoxAPIConnection, String, String...)}. + */ @Test @Category(UnitTest.class) public void getAllEnterpriseUsersRequestsCorrectFilterAndFields() { @@ -62,6 +73,9 @@ public void getAllEnterpriseUsersRequestsCorrectFilterAndFields() { assertThat(usersList.get(0).getName(), is(equalTo(name))); } + /** + * Unit test for {@link BoxUser#getExternalUsers(BoxAPIConnection, String, String...)}. + */ @Test @Category(UnitTest.class) public void getExternalUsersRequestsCorrectFilterAndFields() { @@ -88,6 +102,9 @@ public void getExternalUsersRequestsCorrectFilterAndFields() { assertThat(usersList.get(0).getName(), is(equalTo(name))); } + /** + * Unit test for {@link BoxUser#getAllEnterpriseOrExternalUsers(BoxAPIConnection, String, String...)}. + */ @Test @Category(UnitTest.class) public void getAllEnterpriseOrExternalUsersRequestsCorrectFilterAndFields() { @@ -114,6 +131,9 @@ public void getAllEnterpriseOrExternalUsersRequestsCorrectFilterAndFields() { assertThat(usersList.get(0).getName(), is(equalTo(name))); } + /** + * Unit test for {@link BoxUser#createEnterpriseUser(BoxAPIConnection, String, String)}. + */ @Test @Category(UnitTest.class) public void createEnterpriseUserSendsJSONWithLoginAndName() { @@ -143,6 +163,9 @@ public String getJSON() { BoxUser.Info createdUserInfo = BoxUser.createEnterpriseUser(api, login, name); } + /** + * Unit test for {@link BoxUser#createEnterpriseUser(BoxAPIConnection, String, String, CreateUserParams)}. + */ @Test @Category(UnitTest.class) public void createEnterpriseUserSendsJSONWithAdditionalParams() { @@ -211,6 +234,9 @@ public String getJSON() { BoxUser.Info createdUserInfo = BoxUser.createEnterpriseUser(api, login, name, params); } + /** + * Unit test for {@link BoxUser#createEnterpriseUser(BoxAPIConnection, String, String)}. + */ @Test @Category(UnitTest.class) public void createEnterpriseUserParsesAllFieldsCorrectly() throws ParseException { @@ -272,6 +298,9 @@ public void createEnterpriseUserParsesAllFieldsCorrectly() throws ParseException assertEquals(avatarURL, createdUserInfo.getAvatarURL()); } + /** + * Unit test for {@link BoxUser#updateInfo(BoxUser.Info)}. + */ @Test @Category(UnitTest.class) public void updateInfoSendsCorrectJSON() { @@ -346,6 +375,9 @@ public String getJSON() { user.updateInfo(info); } + /** + * Unit test for {@link BoxUser#getEmailAliases()}. + */ @Test @Category(UnitTest.class) public void getEmailAliasesParsesAllFieldsCorrectly() { @@ -392,6 +424,9 @@ public void getEmailAliasesParsesAllFieldsCorrectly() { } } + /** + * Unit test for {@link BoxUser#addEmailAlias(String)}. + */ @Test @Category(UnitTest.class) public void addEmailAliasSendsCorrectJSON() { @@ -416,6 +451,90 @@ public String getJSON() { user.addEmailAlias(email); } + /** + * Unit test for {@link BoxUser#getAllMemberships(String...)}. + */ + @Test + @Category(UnitTest.class) + public void testGetAllMembershipsSendsCorrectRequest() { + final JsonObject fakeJSONResponse = new JsonObject() + .add("total_count", 0) + .add("offset", 0) + .add("entries", new JsonArray()); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + assertEquals("https://api.box.com/2.0/users/0/memberships?fields=user%2Cgroup&limit=100&offset=0", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return fakeJSONResponse.toString(); + } + }; + } + }); + + BoxUser user = new BoxUser(api, "0"); + Iterator iterator = user.getAllMemberships("user", "group").iterator(); + iterator.hasNext(); + } + + /** + * Unit test for {@link BoxUser#getAllMemberships(String...)}. + */ + @Test + @Category(UnitTest.class) + public void testGetMembershipsParseAllFieldsCorrectly() throws ParseException { + final String id = "1560354"; + final String userID = "13130406"; + final String userName = "Alison Wonderland"; + final String userLogin = "alice@gmail.com"; + final String groupID = "119720"; + final String groupName = "family"; + final BoxGroupMembership.Role role = BoxGroupMembership.Role.MEMBER; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"total_count\": 1,\n" + + " \"entries\": [\n" + + " {\n" + + " \"type\": \"group_membership\",\n" + + " \"id\": \"1560354\",\n" + + " \"user\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"13130406\",\n" + + " \"name\": \"Alison Wonderland\",\n" + + " \"login\": \"alice@gmail.com\"\n" + + " },\n" + + " \"group\": {\n" + + " \"type\": \"group\",\n" + + " \"id\": \"119720\",\n" + + " \"name\": \"family\"\n" + + " },\n" + + " \"role\": \"member\"\n" + + " }\n" + + " ],\n" + + " \"limit\": 100,\n" + + " \"offset\": 0\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + BoxUser user = new BoxUser(api, "0"); + Iterator iterator = user.getAllMemberships().iterator(); + BoxGroupMembership.Info info = iterator.next(); + assertEquals(id, info.getID()); + assertEquals(userID, info.getUser().getID()); + assertEquals(userName, info.getUser().getName()); + assertEquals(userLogin, info.getUser().getLogin()); + assertEquals(groupID, info.getGroup().getID()); + assertEquals(groupName, info.getGroup().getName()); + assertEquals(role, info.getRole()); + assertEquals(false, iterator.hasNext()); + } + @Test @Category(IntegrationTest.class) public void getCurrentUserInfoIsCorrect() throws InterruptedException { From 6d3b05a9ee6071a3572a87d0823d8c94c2a7d1b0 Mon Sep 17 00:00:00 2001 From: gleb-urvanov Date: Mon, 14 Nov 2016 11:17:23 +0100 Subject: [PATCH 008/119] update group feature implemented --- doc/groups.md | 111 ++++- src/main/java/com/box/sdk/BoxGroup.java | 114 +++++ .../java/com/box/sdk/BoxGroupMembership.java | 51 +++ .../box/sdk/BoxGroupMembershipIterator.java | 33 +- src/main/java/com/box/sdk/BoxUser.java | 20 + .../com/box/sdk/BoxGroupMembershipTest.java | 105 ++++- src/test/java/com/box/sdk/BoxGroupTest.java | 411 +++++++++++++++++- src/test/java/com/box/sdk/BoxUserTest.java | 119 +++++ 8 files changed, 956 insertions(+), 8 deletions(-) diff --git a/doc/groups.md b/doc/groups.md index b99f85bf3..5dd66aa4a 100644 --- a/doc/groups.md +++ b/doc/groups.md @@ -5,8 +5,15 @@ Groups are sets of users that can be used in collaborations. * [Get All Groups](#get-all-groups) * [Create a Group](#create-a-group) +* [Update a Group](#update-a-group) * [Delete a Group](#delete-a-group) * [Get a Groups collaborations](#get-a-groups-collaborations) +* [Create Membership](#create-membership) +* [Get Membership](#get-membership) +* [Update Membership](#update-membership) +* [Delete Membership](#delete-membership) +* [Get Memberships for Group](#get-memberships-for-group) +* [Get Memberships for User](#get-memberships-for-user) Get All Groups -------------- @@ -35,6 +42,22 @@ BoxGroup.Info groupInfo = BoxGroup.createGroup(api, "My Group"); [create-group]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxGroup.html#createGroup(com.box.sdk.BoxAPIConnection,%20java.lang.String) + +Update a Group +-------------- + +To update a group, call [`updateInfo(BoxGroup.Info)`][update-group] method. + +```java +BoxGroup group = new BoxGroup(api, id); +BoxGroup.Info groupInfo = group.getInfo(); +groupInfo.addPendingChange("name", "New name for My Group"); +group.updateInfo(groupInfo); +``` + +[update-group]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxGroup.html#updateInfo(com.box.sdk.BoxGroup.Info) + + Delete a Group -------------- @@ -50,11 +73,95 @@ group.delete(); Get a Groups collaborations --------------------------- -A groups collaborations can be retrieved by calling the [`getCollaborations()`[get-collaborations] method. +A groups collaborations can be retrieved by calling the [`getCollaborations()`][get-collaborations] method. ```java BoxGroup group = new BoxGroup(api, "id"); group.getCollaborations(); ``` -[get-collaborations]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxGroup.html#getCollaborations() \ No newline at end of file +[get-collaborations]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxGroup.html#getCollaborations() + +Create Membership +--------------- + +Membership for the group can be created by calling [`addMembership(BoxUser)`][add-membership] and [`addMembership(BoxUser, Role)`][add-membership2] methods. + +```java +BoxGroup group = new BoxGroup(api, "groupID"); +BoxUser user = new BoxUser(api, "userID"); +BoxGroupMembership.Info groupMembershipInfo = group.addMembership(user); +``` + +[add-membership]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxGroup.html#addMembership(com.box.sdk.BoxUser) +[add-membership2]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxGroup.html#addMembership(com.box.sdk.BoxUser,%20com.box.sdk.BoxGroupMembership.Role) + +Get Membership +--------------- + +A groups membership can be retrieved by calling the [`BoxGroupMembership.getInfo()`][get-membership] method. + +```java +BoxGroupMembership membership = new BoxGroupMembership(api, id); +BoxGroupMembership.Info groupMembershipInfo = membership.getInfo(); +``` + +[get-membership]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxGroupMembership.html#getInfo() + +Update Membership +--------------- + +A groups membership can be updated by calling the [`BoxGroupMembership.updateInfo(BoxGroupMembership.Info)`][update-membership] method. + +```java +BoxGroupMembership membership = new BoxGroupMembership(api, id); +BoxGroupMembership.Info info = membership.new Info(); +info.addPendingChange("role", role); +membership.updateInfo(info); +``` + +[update-membership]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxGroupMembership.html#updateInfo(com.box.sdk.BoxGroupMembership.Info) + +Delete Membership +--------------- + +A group can be deleted by calling the [`BoxGroupMembership.delete()`][delete-membership] method. + +```java +BoxGroupMembership membership = new BoxGroupMembership(api, id); +membership.delete(); +``` + +[delete-membership]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxGroupMembership.html#delete() + +Get Memberships for Group +--------------- + +Calling the [`getAllMemberships(String...)`][get-memberships-for-group] will return an iterable that will page through all of the group's memberships. +Optional parameters can be used to retrieve specific fields of the Group Membership object. + +```java +BoxGroup group = new BoxGroup(api, id); +Iterable memberships = group.getAllMemberships(); +for (BoxGroupMembership.Info membershipInfo : memberships) { + // Do something with the membership. +} +``` + +[get-memberships-for-group]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxGroup.html#getAllMemberships(java.lang.String...) + +Get Memberships for User +--------------- + +Calling the [`BoxUser.getAllMemberships(String...)`][get-memberships-for-user] will return an iterable that will page through all of the user's memberships. +Optional parameters can be used to retrieve specific fields of the Group Membership object. + +```java +BoxUser user = new BoxUser(api, id); +Iterable memberships = user.getAllMemberships(); +for (BoxGroupMembership.Info membershipInfo : memberships) { + // Do something with the membership. +} +``` + +[get-memberships-for-user]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxUser.html#getAllMemberships(java.lang.String...) \ No newline at end of file diff --git a/src/main/java/com/box/sdk/BoxGroup.java b/src/main/java/com/box/sdk/BoxGroup.java index 07f8e7cd9..82e259e0b 100644 --- a/src/main/java/com/box/sdk/BoxGroup.java +++ b/src/main/java/com/box/sdk/BoxGroup.java @@ -19,10 +19,30 @@ */ @BoxResourceType("group") public class BoxGroup extends BoxCollaborator { + + /** + * @see #getAllGroups(BoxAPIConnection, String...) + */ private static final URLTemplate GROUPS_URL_TEMPLATE = new URLTemplate("groups"); + + /** + * @see #getInfo() + */ private static final URLTemplate GROUP_URL_TEMPLATE = new URLTemplate("groups/%s"); + + /** + * @see #getMemberships() + */ private static final URLTemplate MEMBERSHIPS_URL_TEMPLATE = new URLTemplate("groups/%s/memberships"); + + /** + * @see #addMembership(BoxUser) + */ private static final URLTemplate ADD_MEMBERSHIP_URL_TEMPLATE = new URLTemplate("group_memberships"); + + /** + * @see #getCollaborations() + */ private static final URLTemplate COLLABORATIONS_URL_TEMPLATE = new URLTemplate("groups/%s/collaborations"); /** @@ -101,6 +121,25 @@ public Iterator iterator() { }; } + /** + * Gets an iterable of all the groups in the enterprise. + * @param api the API connection to be used when retrieving the groups. + * @param fields the fields to retrieve. + * @return an iterable containing info about all the groups. + */ + public static Iterable getAllGroups(final BoxAPIConnection api, String ... fields) { + final QueryStringBuilder builder = new QueryStringBuilder(); + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + return new Iterable() { + public Iterator iterator() { + URL url = GROUPS_URL_TEMPLATE.buildWithQuery(api.getBaseURL(), builder.toString()); + return new BoxGroupIterator(api, url); + } + }; + } + /** * Gets information about this group. * @return info about this group. @@ -113,8 +152,26 @@ public Info getInfo() { return new Info(responseJSON); } + /** + * Gets information about this group. + * @param fields the fields to retrieve. + * @return info about this group. + */ + public Info getInfo(String ... fields) { + QueryStringBuilder builder = new QueryStringBuilder(); + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + URL url = GROUP_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), builder.toString(), this.getID()); + BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); + return new Info(responseJSON); + } + /** * Gets information about all of the group memberships for this group. + * Does not support paging. * @return a collection of information about the group memberships for this group. */ public Collection getMemberships() { @@ -137,6 +194,25 @@ public Iterator iterator() { return memberships; } + /** + * Gets information about all of the group memberships for this group as iterable with paging support. + * @param fields the fields to retrieve. + * @return an iterable with information about the group memberships for this group. + */ + public Iterable getAllMemberships(String ... fields) { + final QueryStringBuilder builder = new QueryStringBuilder(); + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + return new Iterable() { + public Iterator iterator() { + URL url = MEMBERSHIPS_URL_TEMPLATE.buildWithQuery( + BoxGroup.this.getAPI().getBaseURL(), builder.toString(), BoxGroup.this.getID()); + return new BoxGroupMembershipIterator(BoxGroup.this.getAPI(), url); + } + }; + } + /** * Adds a member to this group with the default role. * @param user the member to be added to this group. @@ -207,15 +283,47 @@ public void delete() { response.disconnect(); } + /** + * Updates the information about this group with any info fields that have been modified locally. + * @param info the updated info. + */ + public void updateInfo(BoxGroup.Info info) { + URL url = GROUP_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT"); + request.setBody(info.getPendingChanges()); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + info.update(jsonObject); + } + /** * Contains information about a BoxGroup. */ public class Info extends BoxCollaborator.Info { + /** + * @see #getProvenance() + */ private String provenance; + + /** + * @see #getExternalSyncIdentifier() + */ private String externalSyncIdentifier; + + /** + * @see #getDescription() + */ private String description; + + /** + * @see #getInvitabilityLevel() + */ private String invitabilityLevel; + + /** + * @see #getMemberViewabilityLevel() + */ private String memberViewabilityLevel; /** @@ -241,11 +349,17 @@ public Info(String json) { super(jsonObject); } + /** + * {@inheritDoc} + */ @Override public BoxGroup getResource() { return BoxGroup.this; } + /** + * {@inheritDoc} + */ @Override protected void parseJSONMember(JsonObject.Member member) { super.parseJSONMember(member); diff --git a/src/main/java/com/box/sdk/BoxGroupMembership.java b/src/main/java/com/box/sdk/BoxGroupMembership.java index 2439a3d00..8d4781867 100644 --- a/src/main/java/com/box/sdk/BoxGroupMembership.java +++ b/src/main/java/com/box/sdk/BoxGroupMembership.java @@ -16,6 +16,11 @@ */ @BoxResourceType("group_membership") public class BoxGroupMembership extends BoxResource { + + /** + * The URL template for all group membership requests. + * @see #getInfo() + */ private static final URLTemplate MEMBERSHIP_URL_TEMPLATE = new URLTemplate("group_memberships/%s"); /** @@ -72,10 +77,30 @@ public void delete() { * Contains information about a BoxGroupMembership. */ public class Info extends BoxResource.Info { + + /** + * @see #getUser() + */ private BoxUser.Info user; + + /** + * @see #getGroup() + */ private BoxGroup.Info group; + + /** + * @see #getRole() + */ private Role role; + + /** + * @see #getCreatedAt() + */ private Date createdAt; + + /** + * @see #getModifiedAt() + */ private Date modifiedAt; /** @@ -157,11 +182,17 @@ public Date getModifiedAt() { return this.modifiedAt; } + /** + * {@inheritDoc} + */ @Override public BoxGroupMembership getResource() { return BoxGroupMembership.this; } + /** + * {@inheritDoc} + */ @Override protected void parseJSONMember(JsonObject.Member member) { super.parseJSONMember(member); @@ -215,21 +246,41 @@ public enum Role { */ ADMIN ("admin"), + /** + * The user is a submaster in the group. + */ + SUBMASTER ("submaster"), + /** * The user is a regular member in the group. */ MEMBER ("member"); + /** + * String representation of the role. + */ private final String jsonValue; + /** + * Constructor. + * @param jsonValue srting representation of the role. + */ private Role(String jsonValue) { this.jsonValue = jsonValue; } + /** + * Creates the role from given string. + * @param jsonValue string to be converted to role. + * @return the role, created from string value. + */ static Role fromJSONString(String jsonValue) { return Role.valueOf(jsonValue.toUpperCase()); } + /** + * @return string representation of the role. + */ String toJSONString() { return this.jsonValue; } diff --git a/src/main/java/com/box/sdk/BoxGroupMembershipIterator.java b/src/main/java/com/box/sdk/BoxGroupMembershipIterator.java index 8fe077978..bb135ecc7 100644 --- a/src/main/java/com/box/sdk/BoxGroupMembershipIterator.java +++ b/src/main/java/com/box/sdk/BoxGroupMembershipIterator.java @@ -5,29 +5,58 @@ import com.eclipsesource.json.JsonObject; +/** + * An iterator object for {@link BoxGroupMembership} object. + * Supports offset-based paging. + */ class BoxGroupMembershipIterator implements Iterator { + + /** + * The limit of entries per response. + */ private static final long LIMIT = 1000; + /** + * The API connection to be used. + */ private final BoxAPIConnection api; + + /** + * Iterator object with paging support. + */ private final JSONIterator jsonIterator; + /** + * Creates new BoxGroupMembership iterator. + * @param api The API connection to be used by the iterator. + * @param url The endpoint url. + */ BoxGroupMembershipIterator(BoxAPIConnection api, URL url) { this.api = api; this.jsonIterator = new JSONIterator(api, url, LIMIT); } + /** + * @return false if current element is the last. + */ public boolean hasNext() { return this.jsonIterator.hasNext(); } + /** + * @return next BoxGroupMembeship object in the list. + */ public BoxGroupMembership.Info next() { JsonObject nextJSONObject = this.jsonIterator.next(); String id = nextJSONObject.get("id").asString(); - BoxGroupMembership group = new BoxGroupMembership(this.api, id); - return group.new Info(nextJSONObject); + BoxGroupMembership membership = new BoxGroupMembership(this.api, id); + return membership.new Info(nextJSONObject); } + /** + * Remove operation is not supported. + */ public void remove() { throw new UnsupportedOperationException(); } diff --git a/src/main/java/com/box/sdk/BoxUser.java b/src/main/java/com/box/sdk/BoxUser.java index 369b49a16..c5fa0f64d 100644 --- a/src/main/java/com/box/sdk/BoxUser.java +++ b/src/main/java/com/box/sdk/BoxUser.java @@ -247,6 +247,7 @@ public BoxUser.Info getInfo(String... fields) { /** * Gets information about all of the group memberships for this user. + * Does not support paging. * *

Note: This method is only available to enterprise admins.

* @@ -273,6 +274,25 @@ public Collection getMemberships() { return memberships; } + /** + * Gets information about all of the group memberships for this user as iterable with paging support. + * @param fields the fields to retrieve. + * @return an iterable with information about the group memberships for this user. + */ + public Iterable getAllMemberships(String ... fields) { + final QueryStringBuilder builder = new QueryStringBuilder(); + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + return new Iterable() { + public Iterator iterator() { + URL url = USER_MEMBERSHIPS_URL_TEMPLATE.buildWithQuery( + BoxUser.this.getAPI().getBaseURL(), builder.toString(), BoxUser.this.getID()); + return new BoxGroupMembershipIterator(BoxUser.this.getAPI(), url); + } + }; + } + /** * Adds a new email alias to this user's account. * @param email the email address to add as an alias. diff --git a/src/test/java/com/box/sdk/BoxGroupMembershipTest.java b/src/test/java/com/box/sdk/BoxGroupMembershipTest.java index f6886d42c..f55ca10c9 100644 --- a/src/test/java/com/box/sdk/BoxGroupMembershipTest.java +++ b/src/test/java/com/box/sdk/BoxGroupMembershipTest.java @@ -1,26 +1,45 @@ package com.box.sdk; import java.text.ParseException; +import java.util.Date; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; +import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; -import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.delete; +import static com.github.tomakehurst.wiremock.client.WireMock.deleteRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; import com.box.sdk.BoxGroupMembership.Role; import com.eclipsesource.json.JsonObject; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.junit.WireMockRule; +/** + * {@link BoxGroupMembership} related tests. + */ public class BoxGroupMembershipTest { + + /** + * Wiremock + */ @Rule public WireMockRule wireMockRule = new WireMockRule(8080); + /** + * Unit test for {@link BoxGroupMembership#getInfo()}. + */ @Test @Category(UnitTest.class) public void getInfoSendsCorrectRequestAndParsesResponseCorrectly() throws ParseException { @@ -78,6 +97,9 @@ public void getInfoSendsCorrectRequestAndParsesResponseCorrectly() throws ParseE .withRequestBody(WireMock.equalTo(""))); } + /** + * Unit test for {@link BoxGroupMembership#delete()}. + */ @Test @Category(UnitTest.class) public void deleteMembershipSendsCorrectRequest() { @@ -98,6 +120,87 @@ public void deleteMembershipSendsCorrectRequest() { .withRequestBody(WireMock.equalTo(""))); } + /** + * Unit test for {@link BoxGroupMembership#updateInfo(BoxGroupMembership.Info)}. + */ + @Test + @Category(UnitTest.class) + public void testUpdateInfoSendsCorrectJson() { + final String role = "submaster"; + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals("https://api.box.com/2.0/group_memberships/0", request.getUrl().toString()); + Assert.assertEquals(role, json.get("role").asString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{}"; + } + }; + } + }); + + BoxGroupMembership membership = new BoxGroupMembership(api, "0"); + BoxGroupMembership.Info info = membership.new Info(); + info.addPendingChange("role", role); + membership.updateInfo(info); + } + + /** + * Unit test for {@link BoxGroupMembership#updateInfo(BoxGroupMembership.Info)}. + */ + @Test + @Category(UnitTest.class) + public void testUpdateInfoParseAllFieldsCorrectly() throws ParseException { + final String id = "1560354"; + final String userID = "13130406"; + final String userName = "Alison Wonderland"; + final String userLogin = "alice@gmail.com"; + final String groupID = "119720"; + final String groupName = "family"; + final Role role = Role.SUBMASTER; + final Date createdAt = BoxDateFormat.parse("2013-05-16T15:27:57-07:00"); + final Date modifiedAt = BoxDateFormat.parse("2013-05-16T15:27:57-07:00"); + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"group_membership\",\n" + + " \"id\": \"1560354\",\n" + + " \"user\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"13130406\",\n" + + " \"name\": \"Alison Wonderland\",\n" + + " \"login\": \"alice@gmail.com\"\n" + + " },\n" + + " \"group\": {\n" + + " \"type\": \"group\",\n" + + " \"id\": \"119720\",\n" + + " \"name\": \"family\"\n" + + " },\n" + + " \"role\": \"submaster\",\n" + + " \"created_at\": \"2013-05-16T15:27:57-07:00\",\n" + + " \"modified_at\": \"2013-05-16T15:27:57-07:00\"\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + BoxGroupMembership membership = new BoxGroupMembership(api, id); + BoxGroupMembership.Info info = membership.new Info(); + info.addPendingChange("role", "non-empty"); + membership.updateInfo(info); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(userID, info.getUser().getID()); + Assert.assertEquals(userName, info.getUser().getName()); + Assert.assertEquals(userLogin, info.getUser().getLogin()); + Assert.assertEquals(groupID, info.getGroup().getID()); + Assert.assertEquals(groupName, info.getGroup().getName()); + Assert.assertEquals(role, info.getRole()); + Assert.assertEquals(createdAt, info.getCreatedAt()); + Assert.assertEquals(modifiedAt, info.getModifiedAt()); + } + @Test @Category(IntegrationTest.class) public void getInfoSucceeds() { diff --git a/src/test/java/com/box/sdk/BoxGroupTest.java b/src/test/java/com/box/sdk/BoxGroupTest.java index c74fea72d..b1cfe9986 100644 --- a/src/test/java/com/box/sdk/BoxGroupTest.java +++ b/src/test/java/com/box/sdk/BoxGroupTest.java @@ -2,29 +2,428 @@ import java.text.ParseException; import java.util.Collection; +import java.util.Date; +import java.util.Iterator; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; -import static org.skyscreamer.jsonassert.JSONCompareMode.*; +import static org.skyscreamer.jsonassert.JSONCompareMode.LENIENT; import org.hamcrest.Matchers; +import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; -import static com.github.tomakehurst.wiremock.client.WireMock.*; - +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.delete; +import static com.github.tomakehurst.wiremock.client.WireMock.deleteRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; + +import com.eclipsesource.json.JsonArray; import com.eclipsesource.json.JsonObject; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.junit.WireMockRule; +/** + * {@link BoxGroup} related tests. + */ public class BoxGroupTest { + + /** + * Wiremock + */ @Rule public WireMockRule wireMockRule = new WireMockRule(8080); + /** + * Unit test for {@link BoxGroup#getInfo(String...)}. + */ + @Test + @Category(UnitTest.class) + public void testGetInfoSendsCorrectRequestWithParams() { + final JsonObject fakeJSONResponse = new JsonObject() + .add("id", "0"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals( + "https://api.box.com/2.0/groups/0?fields=name%2Ccreated_at", request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return fakeJSONResponse.toString(); + } + }; + } + }); + + BoxGroup group = new BoxGroup(api, "0"); + group.getInfo("name", "created_at"); + } + + /** + * Unit test for {@link BoxGroup#getInfo()}. + */ + @Test + @Category(UnitTest.class) + public void testGetInfoSendsCorrectRequest() { + final JsonObject fakeJSONResponse = new JsonObject() + .add("id", "0"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals( + "https://api.box.com/2.0/groups/0", request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return fakeJSONResponse.toString(); + } + }; + } + }); + + BoxGroup group = new BoxGroup(api, "0"); + group.getInfo(); + } + + /** + * Unit test for {@link BoxGroup#getInfo()}. + */ + @Test + @Category(UnitTest.class) + public void testGetInfoParseAllFieldsCorrectly() throws ParseException { + final String id = "119720"; + final String name = "Box Employees"; + final Date createdAt = BoxDateFormat.parse("2013-05-16T15:27:57-07:00"); + final Date modifiedAt = BoxDateFormat.parse("2013-05-16T15:27:57-07:00"); + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"group\",\n" + + " \"id\": \"119720\",\n" + + " \"name\": \"Box Employees\",\n" + + " \"created_at\": \"2013-05-16T15:27:57-07:00\",\n" + + " \"modified_at\": \"2013-05-16T15:27:57-07:00\"\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxGroup group = new BoxGroup(api, id); + BoxGroup.Info info = group.getInfo(); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(name, info.getName()); + Assert.assertEquals(createdAt, info.getCreatedAt()); + Assert.assertEquals(modifiedAt, info.getModifiedAt()); + } + + /** + * Unit test for {@link BoxGroup#addMembership(BoxUser)}. + */ + @Test + @Category(UnitTest.class) + public void testAddMembershipSendsCorrectJson() { + final String userID = "1992432"; + final String groupID = "1992433"; + + final JsonObject fakeJSONResponse = new JsonObject().add("id", "0"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals("https://api.box.com/2.0/group_memberships", request.getUrl().toString()); + Assert.assertEquals(userID, json.get("user").asObject().get("id").asString()); + Assert.assertEquals(groupID, json.get("group").asObject().get("id").asString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return fakeJSONResponse.toString(); + } + }; + } + }); + + BoxGroup group = new BoxGroup(api, groupID); + group.addMembership(new BoxUser(api, userID)); + } + + /** + * Unit test for {@link BoxGroup#addMembership(BoxUser)}. + */ + @Test + @Category(UnitTest.class) + public void testAddMembershipParseAllFieldscorrectly() { + final String id = "1560354"; + final String userID = "13130406"; + final String userName = "Alison Wonderland"; + final String userLogin = "alice@gmail.com"; + final String groupID = "119720"; + final String groupName = "family"; + final BoxGroupMembership.Role role = BoxGroupMembership.Role.MEMBER; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"group_membership\",\n" + + " \"id\": \"1560354\",\n" + + " \"user\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"13130406\",\n" + + " \"name\": \"Alison Wonderland\",\n" + + " \"login\": \"alice@gmail.com\"\n" + + " },\n" + + " \"group\": {\n" + + " \"type\": \"group\",\n" + + " \"id\": \"119720\",\n" + + " \"name\": \"family\"\n" + + " },\n" + + " \"role\": \"member\"\n" + + " }"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + BoxGroup group = new BoxGroup(api, id); + BoxGroupMembership.Info info = group.addMembership(new BoxUser(api, "0")); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(userID, info.getUser().getID()); + Assert.assertEquals(userName, info.getUser().getName()); + Assert.assertEquals(userLogin, info.getUser().getLogin()); + Assert.assertEquals(groupID, info.getGroup().getID()); + Assert.assertEquals(groupName, info.getGroup().getName()); + Assert.assertEquals(role, info.getRole()); + } + + /** + * Unit test for {@link BoxGroup#getAllMemberships(String...)}. + */ + @Test + @Category(UnitTest.class) + public void testGetAllMembershipsParseAllFieldsCorrectly() { + final String firstMembershipID = "1560354"; + final String firstMembershipUserID = "13130906"; + final String firstMembershipUserName = "Alice"; + final String firstMembershipUserLogin = "alice@gmail.com"; + final String groupID = "119720"; + final String groupName = "family"; + final BoxGroupMembership.Role role = BoxGroupMembership.Role.MEMBER; + final String secondMembershipID = "1560356"; + final String secondMembershipUserID = "192633962"; + final String secondMembershipUserName = "rabbit"; + final String secondMembershipUserLogin = "rabbit@gmail.com"; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"total_count\": 2,\n" + + " \"entries\": [\n" + + " {\n" + + " \"type\": \"group_membership\",\n" + + " \"id\": \"1560354\",\n" + + " \"user\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"13130906\",\n" + + " \"name\": \"Alice\",\n" + + " \"login\": \"alice@gmail.com\"\n" + + " },\n" + + " \"group\": {\n" + + " \"type\": \"group\",\n" + + " \"id\": \"119720\",\n" + + " \"name\": \"family\"\n" + + " },\n" + + " \"role\": \"member\"\n" + + " },\n" + + " {\n" + + " \"type\": \"group_membership\",\n" + + " \"id\": \"1560356\",\n" + + " \"user\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"192633962\",\n" + + " \"name\": \"rabbit\",\n" + + " \"login\": \"rabbit@gmail.com\"\n" + + " },\n" + + " \"group\": {\n" + + " \"type\": \"group\",\n" + + " \"id\": \"119720\",\n" + + " \"name\": \"family\"\n" + + " },\n" + + " \"role\": \"member\"\n" + + " }\n" + + " ],\n" + + " \"offset\": 0,\n" + + " \"limit\": 100\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + BoxGroup group = new BoxGroup(api, "0"); + Iterator iterator = group.getAllMemberships().iterator(); + BoxGroupMembership.Info info = iterator.next(); + Assert.assertEquals(firstMembershipID, info.getID()); + Assert.assertEquals(firstMembershipUserID, info.getUser().getID()); + Assert.assertEquals(firstMembershipUserName, info.getUser().getName()); + Assert.assertEquals(firstMembershipUserLogin, info.getUser().getLogin()); + Assert.assertEquals(groupID, info.getGroup().getID()); + Assert.assertEquals(groupName, info.getGroup().getName()); + Assert.assertEquals(role, info.getRole()); + info = iterator.next(); + Assert.assertEquals(secondMembershipID, info.getID()); + Assert.assertEquals(secondMembershipUserID, info.getUser().getID()); + Assert.assertEquals(secondMembershipUserName, info.getUser().getName()); + Assert.assertEquals(secondMembershipUserLogin, info.getUser().getLogin()); + Assert.assertEquals(groupID, info.getGroup().getID()); + Assert.assertEquals(groupName, info.getGroup().getName()); + Assert.assertEquals(role, info.getRole()); + Assert.assertEquals(false, iterator.hasNext()); + } + + /** + * Unit test for {@link BoxGroup#getAllMemberships(String...)}. + */ + @Test + @Category(UnitTest.class) + public void testGetAllMembershipsSendsCorrectRequest() { + final JsonObject fakeJSONResponse = new JsonObject() + .add("total_count", 0) + .add("offset", 0) + .add("entries", new JsonArray()); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals( + "https://api.box.com/2.0/groups/0/memberships?fields=user%2Cgroup&limit=1000&offset=0", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return fakeJSONResponse.toString(); + } + }; + } + }); + + BoxGroup group = new BoxGroup(api, "0"); + Iterator iterator = group.getAllMemberships("user", "group").iterator(); + iterator.hasNext(); + } + + /** + * Unit test for {@link BoxGroup#getAllGroups(BoxAPIConnection, String...)}. + */ + @Test + @Category(UnitTest.class) + public void testGetAllGroupsSendsCorrectRequest() { + final JsonObject fakeJSONResponse = new JsonObject() + .add("total_count", 0) + .add("offset", 0) + .add("entries", new JsonArray()); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/groups?fields=name%2Ccreated_at&limit=1000&offset=0", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return fakeJSONResponse.toString(); + } + }; + } + }); + + Iterator iterator = BoxGroup.getAllGroups(api, "name", "created_at").iterator(); + iterator.hasNext(); + } + + /** + * Unit test for {@link BoxGroup#updateInfo(BoxGroup.Info)}. + */ + @Test + @Category(UnitTest.class) + public void testUpdateInfoSendsCorrectJSON() { + final String name = "family"; + final String provenance = "non-empty provenance"; + final String externalSyncIdentifier = "non-empty identifier"; + final String description = "non-empty description"; + final String invitabilityLevel = "non-empty level"; + final String memberViewabilityLevel = "another non-empty level"; + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals("https://api.box.com/2.0/groups/0", request.getUrl().toString()); + Assert.assertEquals(name, json.get("name").asString()); + Assert.assertEquals(provenance, json.get("provenance").asString()); + Assert.assertEquals(externalSyncIdentifier, json.get("external_sync_identifier").asString()); + Assert.assertEquals(description, json.get("description").asString()); + Assert.assertEquals(invitabilityLevel, json.get("invitability_level").asString()); + Assert.assertEquals(memberViewabilityLevel, json.get("member_viewability_level").asString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{}"; + } + }; + } + }); + + BoxGroup group = new BoxGroup(api, "0"); + BoxGroup.Info info = group.new Info(); + info.addPendingChange("name", name); + info.addPendingChange("provenance", provenance); + info.addPendingChange("external_sync_identifier", externalSyncIdentifier); + info.addPendingChange("description", description); + info.addPendingChange("invitability_level", invitabilityLevel); + info.addPendingChange("member_viewability_level", memberViewabilityLevel); + group.updateInfo(info); + } + + /** + * Unit test for {@link BoxGroup#updateInfo(BoxGroup.Info)}. + */ + @Test + @Category(UnitTest.class) + public void testUpdateInfoParseAllFieldsCorrectly() throws ParseException { + final String id = "119720"; + final String name = "family"; + final Date createdAt = BoxDateFormat.parse("2013-05-16T15:27:57-07:00"); + final Date modifiedAt = BoxDateFormat.parse("2013-05-17T15:27:57-07:00"); + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"group\",\n" + + " \"id\": \"119720\",\n" + + " \"name\": \"family\",\n" + + " \"created_at\": \"2013-05-16T15:27:57-07:00\",\n" + + " \"modified_at\": \"2013-05-17T15:27:57-07:00\"\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxGroup group = new BoxGroup(api, id); + BoxGroup.Info info = group.new Info(); + info.addPendingChange("name", name); + group.updateInfo(info); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(name, info.getName()); + Assert.assertEquals(createdAt, info.getCreatedAt()); + Assert.assertEquals(modifiedAt, info.getModifiedAt()); + } + @Test @Category(IntegrationTest.class) public void createAndDeleteGroupSucceeds() { @@ -74,6 +473,9 @@ public void getInfoSucceeds() { createdGroupInfo.getResource().delete(); } + /** + * Unit test for {@link BoxGroup#createGroup(BoxAPIConnection, String, String, String, String, String, String)}. + */ @Test @Category(UnitTest.class) public void createGroupSendsCorrectRequestAndParsesResponseCorrectly() throws ParseException { @@ -131,6 +533,9 @@ public void createGroupSendsCorrectRequestAndParsesResponseCorrectly() throws Pa .withRequestBody(equalToJson(expectedJSON.toString(), LENIENT))); } + /** + * Unit test for {@link BoxGroup#delete()}. + */ @Test @Category(UnitTest.class) public void deleteGroupSendsCorrectRequest() { diff --git a/src/test/java/com/box/sdk/BoxUserTest.java b/src/test/java/com/box/sdk/BoxUserTest.java index 84cf83c1f..7aef4ccea 100644 --- a/src/test/java/com/box/sdk/BoxUserTest.java +++ b/src/test/java/com/box/sdk/BoxUserTest.java @@ -3,6 +3,7 @@ import java.text.ParseException; import java.util.Collection; import java.util.Date; +import java.util.Iterator; import java.util.List; import static org.hamcrest.Matchers.equalTo; @@ -33,10 +34,20 @@ import com.github.tomakehurst.wiremock.junit.WireMockRule; import com.google.common.collect.Lists; +/** + * {@link BoxUser} related tests. + */ public class BoxUserTest { + + /** + * Wiremock + */ @Rule public final WireMockRule wireMockRule = new WireMockRule(8080); + /** + * Unit test for {@link BoxUser#getAllEnterpriseUsers(BoxAPIConnection, String, String...)}. + */ @Test @Category(UnitTest.class) public void getAllEnterpriseUsersRequestsCorrectFilterAndFields() { @@ -62,6 +73,9 @@ public void getAllEnterpriseUsersRequestsCorrectFilterAndFields() { assertThat(usersList.get(0).getName(), is(equalTo(name))); } + /** + * Unit test for {@link BoxUser#getExternalUsers(BoxAPIConnection, String, String...)}. + */ @Test @Category(UnitTest.class) public void getExternalUsersRequestsCorrectFilterAndFields() { @@ -88,6 +102,9 @@ public void getExternalUsersRequestsCorrectFilterAndFields() { assertThat(usersList.get(0).getName(), is(equalTo(name))); } + /** + * Unit test for {@link BoxUser#getAllEnterpriseOrExternalUsers(BoxAPIConnection, String, String...)}. + */ @Test @Category(UnitTest.class) public void getAllEnterpriseOrExternalUsersRequestsCorrectFilterAndFields() { @@ -114,6 +131,9 @@ public void getAllEnterpriseOrExternalUsersRequestsCorrectFilterAndFields() { assertThat(usersList.get(0).getName(), is(equalTo(name))); } + /** + * Unit test for {@link BoxUser#createEnterpriseUser(BoxAPIConnection, String, String)}. + */ @Test @Category(UnitTest.class) public void createEnterpriseUserSendsJSONWithLoginAndName() { @@ -143,6 +163,9 @@ public String getJSON() { BoxUser.Info createdUserInfo = BoxUser.createEnterpriseUser(api, login, name); } + /** + * Unit test for {@link BoxUser#createEnterpriseUser(BoxAPIConnection, String, String, CreateUserParams)}. + */ @Test @Category(UnitTest.class) public void createEnterpriseUserSendsJSONWithAdditionalParams() { @@ -211,6 +234,9 @@ public String getJSON() { BoxUser.Info createdUserInfo = BoxUser.createEnterpriseUser(api, login, name, params); } + /** + * Unit test for {@link BoxUser#createEnterpriseUser(BoxAPIConnection, String, String)}. + */ @Test @Category(UnitTest.class) public void createEnterpriseUserParsesAllFieldsCorrectly() throws ParseException { @@ -272,6 +298,9 @@ public void createEnterpriseUserParsesAllFieldsCorrectly() throws ParseException assertEquals(avatarURL, createdUserInfo.getAvatarURL()); } + /** + * Unit test for {@link BoxUser#updateInfo(BoxUser.Info)}. + */ @Test @Category(UnitTest.class) public void updateInfoSendsCorrectJSON() { @@ -346,6 +375,9 @@ public String getJSON() { user.updateInfo(info); } + /** + * Unit test for {@link BoxUser#getEmailAliases()}. + */ @Test @Category(UnitTest.class) public void getEmailAliasesParsesAllFieldsCorrectly() { @@ -392,6 +424,9 @@ public void getEmailAliasesParsesAllFieldsCorrectly() { } } + /** + * Unit test for {@link BoxUser#addEmailAlias(String)}. + */ @Test @Category(UnitTest.class) public void addEmailAliasSendsCorrectJSON() { @@ -416,6 +451,90 @@ public String getJSON() { user.addEmailAlias(email); } + /** + * Unit test for {@link BoxUser#getAllMemberships(String...)}. + */ + @Test + @Category(UnitTest.class) + public void testGetAllMembershipsSendsCorrectRequest() { + final JsonObject fakeJSONResponse = new JsonObject() + .add("total_count", 0) + .add("offset", 0) + .add("entries", new JsonArray()); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + assertEquals("https://api.box.com/2.0/users/0/memberships?fields=user%2Cgroup&limit=1000&offset=0", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return fakeJSONResponse.toString(); + } + }; + } + }); + + BoxUser user = new BoxUser(api, "0"); + Iterator iterator = user.getAllMemberships("user", "group").iterator(); + iterator.hasNext(); + } + + /** + * Unit test for {@link BoxUser#getAllMemberships(String...)}. + */ + @Test + @Category(UnitTest.class) + public void testGetMembershipsParseAllFieldsCorrectly() throws ParseException { + final String id = "1560354"; + final String userID = "13130406"; + final String userName = "Alison Wonderland"; + final String userLogin = "alice@gmail.com"; + final String groupID = "119720"; + final String groupName = "family"; + final BoxGroupMembership.Role role = BoxGroupMembership.Role.MEMBER; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"total_count\": 1,\n" + + " \"entries\": [\n" + + " {\n" + + " \"type\": \"group_membership\",\n" + + " \"id\": \"1560354\",\n" + + " \"user\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"13130406\",\n" + + " \"name\": \"Alison Wonderland\",\n" + + " \"login\": \"alice@gmail.com\"\n" + + " },\n" + + " \"group\": {\n" + + " \"type\": \"group\",\n" + + " \"id\": \"119720\",\n" + + " \"name\": \"family\"\n" + + " },\n" + + " \"role\": \"member\"\n" + + " }\n" + + " ],\n" + + " \"limit\": 100,\n" + + " \"offset\": 0\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + BoxUser user = new BoxUser(api, "0"); + Iterator iterator = user.getAllMemberships().iterator(); + BoxGroupMembership.Info info = iterator.next(); + assertEquals(id, info.getID()); + assertEquals(userID, info.getUser().getID()); + assertEquals(userName, info.getUser().getName()); + assertEquals(userLogin, info.getUser().getLogin()); + assertEquals(groupID, info.getGroup().getID()); + assertEquals(groupName, info.getGroup().getName()); + assertEquals(role, info.getRole()); + assertEquals(false, iterator.hasNext()); + } + @Test @Category(IntegrationTest.class) public void getCurrentUserInfoIsCorrect() throws InterruptedException { From 0b354f9c02f1b2fcb5c97e9b280414d2ceffb60f Mon Sep 17 00:00:00 2001 From: gleb-urvanov Date: Fri, 11 Nov 2016 17:48:09 +0100 Subject: [PATCH 009/119] get all metadata on file feature implementation and testing --- doc/files.md | 15 +++ src/main/java/com/box/sdk/BoxFile.java | 17 +++ src/main/java/com/box/sdk/BoxItem.java | 7 ++ src/main/java/com/box/sdk/Metadata.java | 41 +++++++ src/test/java/com/box/sdk/BoxFileTest.java | 128 ++++++++++++++++++++- 5 files changed, 206 insertions(+), 2 deletions(-) diff --git a/doc/files.md b/doc/files.md index 24f959a87..6b7113c74 100644 --- a/doc/files.md +++ b/doc/files.md @@ -25,6 +25,7 @@ file's contents, upload new versions, and perform other common file operations * [Get Metadata](#get-metadata) * [Update Metadata](#update-metadata) * [Delete Metadata](#delete-metadata) +* [Get All Metadata on File](#get-all-metadata-on-file) Get a File's Information ------------------------ @@ -357,3 +358,17 @@ file.deleteMetadata(); [delete-metadata-2]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFile.html#deleteMetadata(java.lang.String) [delete-metadata-3]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFile.html#deleteMetadata(java.lang.String,%20java.lang.String) +Get All Metadata on File +-------------- + +[`getAllMetadata(String...)`][get-all-metadata] method will return an iterable that will page through all of the metadata associated with the file. + +```java +BoxFile file = new BoxFile(api, "id"); +Iterable metadataList = file.getAllMetadata("name", "description"); +for (Metadata metadata : metadataList) { + // Do something with the metadata. +} +``` + +[get-all-metadata]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFile.html#getAllMetadata(java.lang.String...) \ No newline at end of file diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 8530ac0ae..29efcd4dc 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -75,6 +75,14 @@ public BoxFile(BoxAPIConnection api, String id) { super(api, id); } + /** + * {@inheritDoc} + */ + @Override + protected URL getItemURL() { + return FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); + } + @Override public BoxSharedLink createSharedLink(BoxSharedLink.Access access, Date unshareDate, BoxSharedLink.Permissions permissions) { @@ -678,6 +686,15 @@ public void unlock() { request.send(); } + /** + * Used to retrieve all metadata associated with the file. + * @param fields the optional fields to retrieve. + * @return An iterable of metadata instances associated with the file. + */ + public Iterable getAllMetadata(String ... fields) { + return Metadata.getAllMetadata(this, fields); + } + /** * Gets the file properties metadata. * @return the metadata returned from the server. diff --git a/src/main/java/com/box/sdk/BoxItem.java b/src/main/java/com/box/sdk/BoxItem.java index 7248b1a32..073f13890 100644 --- a/src/main/java/com/box/sdk/BoxItem.java +++ b/src/main/java/com/box/sdk/BoxItem.java @@ -37,6 +37,13 @@ public BoxItem(BoxAPIConnection api, String id) { super(api, id); } + /** + * @return URL for the current object, constructed as base URL pus an item specifier. + */ + protected URL getItemURL() { + return new URLTemplate("").build(this.getAPI().getBaseURL()); + } + /** * Gets an item that was shared with a shared link. * @param api the API connection to be used by the shared item. diff --git a/src/main/java/com/box/sdk/Metadata.java b/src/main/java/com/box/sdk/Metadata.java index 40a87bf81..02f446450 100644 --- a/src/main/java/com/box/sdk/Metadata.java +++ b/src/main/java/com/box/sdk/Metadata.java @@ -30,7 +30,24 @@ public class Metadata { */ public static final String ENTERPRISE_METADATA_SCOPE = "enterprise"; + /** + * The default limit of entries per response. + */ + public static final int DEFAULT_LIMIT = 100; + + /** + * URL template for all metadata associated with item. + */ + private static final URLTemplate GET_ALL_METADATA_URL_TEMPLATE = new URLTemplate("/metadata"); + + /** + * Values contained by the metadata object. + */ private final JsonObject values; + + /** + * Operations to be applied to the metadata object. + */ private JsonArray operations; /** @@ -56,6 +73,30 @@ public Metadata(Metadata other) { this.values = new JsonObject(other.values); } + /** + * Used to retrieve all metadata associated with the item. + * @param item item to get metadata for. + * @param fields the optional fields to retrieve. + * @return An iterable of metadata instances associated with the item. + */ + public static Iterable getAllMetadata(BoxItem item, String ... fields) { + QueryStringBuilder builder = new QueryStringBuilder(); + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + return new BoxResourceIterable( + item.getAPI(), + GET_ALL_METADATA_URL_TEMPLATE.buildWithQuery(item.getItemURL().toString(), builder.toString()), + DEFAULT_LIMIT) { + + @Override + protected Metadata factory(JsonObject jsonObject) { + return new Metadata(jsonObject); + } + + }; + } + /** * Returns the 36 character UUID to identify the metadata object. * @return the metadata ID. diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index 85a039286..1434faf1d 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -15,8 +15,17 @@ import java.util.Calendar; import java.util.Collection; import java.util.Date; - -import static org.hamcrest.Matchers.*; +import java.util.Iterator; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isEmptyOrNullString; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.anyLong; @@ -30,7 +39,122 @@ import org.junit.Test; import org.junit.experimental.categories.Category; +import com.eclipsesource.json.JsonObject; + +/** + * {@link BoxFile} related unit tests. + */ public class BoxFileTest { + + /** + * Unit test for {@link BoxFile#getAllMetadata(String...)}. + */ + @Test + @Category(UnitTest.class) + public void testGetAllMetadataSendsCorrectRequest() { + final BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals( + "https://api.box.com/2.0/files/5010739061/metadata?fields=name%2Cdescription&limit=100", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"entries\": []}"; + } + }; + } + }); + + BoxFile file = new BoxFile(api, "5010739061"); + Iterator iterator = file.getAllMetadata("name", "description").iterator(); + iterator.hasNext(); + } + + /** + * Unit test for {@link BoxFile#getAllMetadata(String...)}. + */ + @Test + @Category(UnitTest.class) + public void testGetAllMetadateParseAllFieldsCorrectly() { + final String firstEntrycurrentDocumentStage = "Init"; + final String firstEntryType = "documentFlow-452b4c9d-c3ad-4ac7-b1ad-9d5192f2fc5f"; + final String firstEntryParent = "file_5010739061"; + final String firstEntryID = "50ba0dba-0f89-4395-b867-3e057c1f6ed9"; + final int firstEntryVersion = 4; + final int firstEntryTypeVersion = 2; + final String firstEntryNeedApprovalFrom = "Smith"; + final String firstEntryTemplate = "documentFlow"; + final String firstEntryScope = "enterprise_12345"; + final String secondEntryType = "productInfo-9d7b6993-b09e-4e52-b197-e42f0ea995b9"; + final String secondEntryParent = "file_5010739061"; + final String secondEntryID = "15d1014a-06c2-47ad-9916-014eab456194"; + final int secondEntryVersion = 2; + final int secondEntryTypeVersion = 1; + final int secondEntrySkuNumber = 45334223; + final String secondEntryDescription = "Watch"; + final String secondEntryTemplate = "productInfo"; + final String secondEntryScope = "enterprise_12345"; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"entries\": [\n" + + " {\n" + + " \"currentDocumentStage\": \"Init\",\n" + + " \"$type\": \"documentFlow-452b4c9d-c3ad-4ac7-b1ad-9d5192f2fc5f\",\n" + + " \"$parent\": \"file_5010739061\",\n" + + " \"$id\": \"50ba0dba-0f89-4395-b867-3e057c1f6ed9\",\n" + + " \"$version\": 4,\n" + + " \"$typeVersion\": 2,\n" + + " \"needsApprovalFrom\": \"Smith\",\n" + + " \"$template\": \"documentFlow\",\n" + + " \"$scope\": \"enterprise_12345\"\n" + + " },\n" + + " {\n" + + " \"$type\": \"productInfo-9d7b6993-b09e-4e52-b197-e42f0ea995b9\",\n" + + " \"$parent\": \"file_5010739061\",\n" + + " \"$id\": \"15d1014a-06c2-47ad-9916-014eab456194\",\n" + + " \"$version\": 2,\n" + + " \"$typeVersion\": 1,\n" + + " \"skuNumber\": 45334223,\n" + + " \"description\": \"Watch\",\n" + + " \"$template\": \"productInfo\",\n" + + " \"$scope\": \"enterprise_12345\"\n" + + " }\n" + + "\n" + + " ],\n" + + " \"limit\": 100\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxFile file = new BoxFile(api, "0"); + Iterator iterator = file.getAllMetadata().iterator(); + Metadata entry = iterator.next(); + Assert.assertEquals(firstEntrycurrentDocumentStage, entry.get("/currentDocumentStage")); + Assert.assertEquals(firstEntryType, entry.getTypeName()); + Assert.assertEquals(firstEntryParent, entry.getParentID()); + Assert.assertEquals(firstEntryID, entry.getID()); + Assert.assertEquals(firstEntryVersion, (int) Integer.valueOf(entry.get("/$version"))); + Assert.assertEquals(firstEntryTypeVersion, (int) Integer.valueOf(entry.get("/$typeVersion"))); + Assert.assertEquals(firstEntryNeedApprovalFrom, entry.get("/needsApprovalFrom")); + Assert.assertEquals(firstEntryTemplate, entry.getTemplateName()); + Assert.assertEquals(firstEntryScope, entry.getScope()); + entry = iterator.next(); + Assert.assertEquals(secondEntryType, entry.getTypeName()); + Assert.assertEquals(secondEntryParent, entry.getParentID()); + Assert.assertEquals(secondEntryID, entry.getID()); + Assert.assertEquals(secondEntryVersion, (int) Integer.valueOf(entry.get("/$version"))); + Assert.assertEquals(secondEntryTypeVersion, (int) Integer.valueOf(entry.get("/$typeVersion"))); + Assert.assertEquals(secondEntrySkuNumber, (int) Integer.valueOf(entry.get("/skuNumber"))); + Assert.assertEquals(secondEntryDescription, entry.get("/description")); + Assert.assertEquals(secondEntryTemplate, entry.getTemplateName()); + Assert.assertEquals(secondEntryScope, entry.getScope()); + + } + @Test @Category(IntegrationTest.class) public void uploadAndDownloadFileSucceeds() throws IOException { From da6dd1abde6c967e1689b7bcb9159cd845bd2826 Mon Sep 17 00:00:00 2001 From: gleb-urvanov Date: Mon, 14 Nov 2016 10:53:20 +0100 Subject: [PATCH 010/119] get all metadata on folder feature implementation --- doc/folders.md | 16 +++ src/main/java/com/box/sdk/BoxFile.java | 17 +++ src/main/java/com/box/sdk/BoxFolder.java | 17 +++ src/main/java/com/box/sdk/BoxItem.java | 7 + src/main/java/com/box/sdk/Metadata.java | 41 ++++++ src/test/java/com/box/sdk/BoxFileTest.java | 128 ++++++++++++++++++- src/test/java/com/box/sdk/BoxFolderTest.java | 127 +++++++++++++++++- 7 files changed, 348 insertions(+), 5 deletions(-) diff --git a/doc/folders.md b/doc/folders.md index 1df932d0f..63e1fbbd5 100644 --- a/doc/folders.md +++ b/doc/folders.md @@ -21,6 +21,7 @@ group, and perform other common folder operations (move, copy, delete, etc.). * [Get Metadata](#get-metadata) * [Update Metadata](#update-metadata) * [Delete Metadata](#delete-metadata) +* [Get All Metadata on Folder](#get-all-metadata-on-folder) Get the User's Root Folder -------------------------- @@ -329,3 +330,18 @@ folder.deleteMetadata(); [delete-metadata]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFolder.html#deleteMetadata() [delete-metadata-2]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFolder.html#deleteMetadata(java.lang.String) [delete-metadata-3]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFolder.html#deleteMetadata(java.lang.String,%20java.lang.String) + +Get All Metadata on Folder +------------------------- + +[`getAllMetadata(String...)`][get-all-metadata] method will return an iterable that will page through all of the metadata associated with the folder. + +```java +BoxFolder file = new BoxFolder(api, "id"); +Iterable metadataList = folder.getAllMetadata("name", "description"); +for (Metadata metadata : metadataList) { + // Do something with the metadata. +} +``` + +[get-all-metadata]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFolder.html#getAllMetadata(java.lang.String...) \ No newline at end of file diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 8530ac0ae..29efcd4dc 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -75,6 +75,14 @@ public BoxFile(BoxAPIConnection api, String id) { super(api, id); } + /** + * {@inheritDoc} + */ + @Override + protected URL getItemURL() { + return FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); + } + @Override public BoxSharedLink createSharedLink(BoxSharedLink.Access access, Date unshareDate, BoxSharedLink.Permissions permissions) { @@ -678,6 +686,15 @@ public void unlock() { request.send(); } + /** + * Used to retrieve all metadata associated with the file. + * @param fields the optional fields to retrieve. + * @return An iterable of metadata instances associated with the file. + */ + public Iterable getAllMetadata(String ... fields) { + return Metadata.getAllMetadata(this, fields); + } + /** * Gets the file properties metadata. * @return the metadata returned from the server. diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index 8d8c70010..2c00e8784 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -52,6 +52,14 @@ public BoxFolder(BoxAPIConnection api, String id) { super(api, id); } + /** + * {@inheritDoc} + */ + @Override + protected URL getItemURL() { + return FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); + } + /** * Gets the current user's root folder. * @param api the API connection to be used by the folder. @@ -532,6 +540,15 @@ public BoxWebHook.Info addWebHook(URL address, BoxWebHook.Trigger... triggers) { return BoxWebHook.create(this, address, triggers); } + /** + * Used to retrieve all metadata associated with the folder. + * @param fields the optional fields to retrieve. + * @return An iterable of metadata instances associated with the folder + */ + public Iterable getAllMetadata(String ... fields) { + return Metadata.getAllMetadata(this, fields); + } + /** * This method is deprecated, please use the {@link BoxSearch} class instead. * Searches this folder and all descendant folders using a given queryPlease use BoxSearch Instead. diff --git a/src/main/java/com/box/sdk/BoxItem.java b/src/main/java/com/box/sdk/BoxItem.java index 7248b1a32..073f13890 100644 --- a/src/main/java/com/box/sdk/BoxItem.java +++ b/src/main/java/com/box/sdk/BoxItem.java @@ -37,6 +37,13 @@ public BoxItem(BoxAPIConnection api, String id) { super(api, id); } + /** + * @return URL for the current object, constructed as base URL pus an item specifier. + */ + protected URL getItemURL() { + return new URLTemplate("").build(this.getAPI().getBaseURL()); + } + /** * Gets an item that was shared with a shared link. * @param api the API connection to be used by the shared item. diff --git a/src/main/java/com/box/sdk/Metadata.java b/src/main/java/com/box/sdk/Metadata.java index 40a87bf81..02f446450 100644 --- a/src/main/java/com/box/sdk/Metadata.java +++ b/src/main/java/com/box/sdk/Metadata.java @@ -30,7 +30,24 @@ public class Metadata { */ public static final String ENTERPRISE_METADATA_SCOPE = "enterprise"; + /** + * The default limit of entries per response. + */ + public static final int DEFAULT_LIMIT = 100; + + /** + * URL template for all metadata associated with item. + */ + private static final URLTemplate GET_ALL_METADATA_URL_TEMPLATE = new URLTemplate("/metadata"); + + /** + * Values contained by the metadata object. + */ private final JsonObject values; + + /** + * Operations to be applied to the metadata object. + */ private JsonArray operations; /** @@ -56,6 +73,30 @@ public Metadata(Metadata other) { this.values = new JsonObject(other.values); } + /** + * Used to retrieve all metadata associated with the item. + * @param item item to get metadata for. + * @param fields the optional fields to retrieve. + * @return An iterable of metadata instances associated with the item. + */ + public static Iterable getAllMetadata(BoxItem item, String ... fields) { + QueryStringBuilder builder = new QueryStringBuilder(); + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + return new BoxResourceIterable( + item.getAPI(), + GET_ALL_METADATA_URL_TEMPLATE.buildWithQuery(item.getItemURL().toString(), builder.toString()), + DEFAULT_LIMIT) { + + @Override + protected Metadata factory(JsonObject jsonObject) { + return new Metadata(jsonObject); + } + + }; + } + /** * Returns the 36 character UUID to identify the metadata object. * @return the metadata ID. diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index 85a039286..1434faf1d 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -15,8 +15,17 @@ import java.util.Calendar; import java.util.Collection; import java.util.Date; - -import static org.hamcrest.Matchers.*; +import java.util.Iterator; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isEmptyOrNullString; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.anyLong; @@ -30,7 +39,122 @@ import org.junit.Test; import org.junit.experimental.categories.Category; +import com.eclipsesource.json.JsonObject; + +/** + * {@link BoxFile} related unit tests. + */ public class BoxFileTest { + + /** + * Unit test for {@link BoxFile#getAllMetadata(String...)}. + */ + @Test + @Category(UnitTest.class) + public void testGetAllMetadataSendsCorrectRequest() { + final BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals( + "https://api.box.com/2.0/files/5010739061/metadata?fields=name%2Cdescription&limit=100", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"entries\": []}"; + } + }; + } + }); + + BoxFile file = new BoxFile(api, "5010739061"); + Iterator iterator = file.getAllMetadata("name", "description").iterator(); + iterator.hasNext(); + } + + /** + * Unit test for {@link BoxFile#getAllMetadata(String...)}. + */ + @Test + @Category(UnitTest.class) + public void testGetAllMetadateParseAllFieldsCorrectly() { + final String firstEntrycurrentDocumentStage = "Init"; + final String firstEntryType = "documentFlow-452b4c9d-c3ad-4ac7-b1ad-9d5192f2fc5f"; + final String firstEntryParent = "file_5010739061"; + final String firstEntryID = "50ba0dba-0f89-4395-b867-3e057c1f6ed9"; + final int firstEntryVersion = 4; + final int firstEntryTypeVersion = 2; + final String firstEntryNeedApprovalFrom = "Smith"; + final String firstEntryTemplate = "documentFlow"; + final String firstEntryScope = "enterprise_12345"; + final String secondEntryType = "productInfo-9d7b6993-b09e-4e52-b197-e42f0ea995b9"; + final String secondEntryParent = "file_5010739061"; + final String secondEntryID = "15d1014a-06c2-47ad-9916-014eab456194"; + final int secondEntryVersion = 2; + final int secondEntryTypeVersion = 1; + final int secondEntrySkuNumber = 45334223; + final String secondEntryDescription = "Watch"; + final String secondEntryTemplate = "productInfo"; + final String secondEntryScope = "enterprise_12345"; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"entries\": [\n" + + " {\n" + + " \"currentDocumentStage\": \"Init\",\n" + + " \"$type\": \"documentFlow-452b4c9d-c3ad-4ac7-b1ad-9d5192f2fc5f\",\n" + + " \"$parent\": \"file_5010739061\",\n" + + " \"$id\": \"50ba0dba-0f89-4395-b867-3e057c1f6ed9\",\n" + + " \"$version\": 4,\n" + + " \"$typeVersion\": 2,\n" + + " \"needsApprovalFrom\": \"Smith\",\n" + + " \"$template\": \"documentFlow\",\n" + + " \"$scope\": \"enterprise_12345\"\n" + + " },\n" + + " {\n" + + " \"$type\": \"productInfo-9d7b6993-b09e-4e52-b197-e42f0ea995b9\",\n" + + " \"$parent\": \"file_5010739061\",\n" + + " \"$id\": \"15d1014a-06c2-47ad-9916-014eab456194\",\n" + + " \"$version\": 2,\n" + + " \"$typeVersion\": 1,\n" + + " \"skuNumber\": 45334223,\n" + + " \"description\": \"Watch\",\n" + + " \"$template\": \"productInfo\",\n" + + " \"$scope\": \"enterprise_12345\"\n" + + " }\n" + + "\n" + + " ],\n" + + " \"limit\": 100\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxFile file = new BoxFile(api, "0"); + Iterator iterator = file.getAllMetadata().iterator(); + Metadata entry = iterator.next(); + Assert.assertEquals(firstEntrycurrentDocumentStage, entry.get("/currentDocumentStage")); + Assert.assertEquals(firstEntryType, entry.getTypeName()); + Assert.assertEquals(firstEntryParent, entry.getParentID()); + Assert.assertEquals(firstEntryID, entry.getID()); + Assert.assertEquals(firstEntryVersion, (int) Integer.valueOf(entry.get("/$version"))); + Assert.assertEquals(firstEntryTypeVersion, (int) Integer.valueOf(entry.get("/$typeVersion"))); + Assert.assertEquals(firstEntryNeedApprovalFrom, entry.get("/needsApprovalFrom")); + Assert.assertEquals(firstEntryTemplate, entry.getTemplateName()); + Assert.assertEquals(firstEntryScope, entry.getScope()); + entry = iterator.next(); + Assert.assertEquals(secondEntryType, entry.getTypeName()); + Assert.assertEquals(secondEntryParent, entry.getParentID()); + Assert.assertEquals(secondEntryID, entry.getID()); + Assert.assertEquals(secondEntryVersion, (int) Integer.valueOf(entry.get("/$version"))); + Assert.assertEquals(secondEntryTypeVersion, (int) Integer.valueOf(entry.get("/$typeVersion"))); + Assert.assertEquals(secondEntrySkuNumber, (int) Integer.valueOf(entry.get("/skuNumber"))); + Assert.assertEquals(secondEntryDescription, entry.get("/description")); + Assert.assertEquals(secondEntryTemplate, entry.getTemplateName()); + Assert.assertEquals(secondEntryScope, entry.getScope()); + + } + @Test @Category(IntegrationTest.class) public void uploadAndDownloadFileSucceeds() throws IOException { diff --git a/src/test/java/com/box/sdk/BoxFolderTest.java b/src/test/java/com/box/sdk/BoxFolderTest.java index 1085216ab..8f010a1a5 100644 --- a/src/test/java/com/box/sdk/BoxFolderTest.java +++ b/src/test/java/com/box/sdk/BoxFolderTest.java @@ -5,7 +5,12 @@ import java.net.MalformedURLException; import java.net.URL; import java.text.SimpleDateFormat; -import java.util.*; +import java.util.Collection; +import java.util.Date; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.List; +import java.util.TimeZone; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; @@ -19,14 +24,22 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.skyscreamer.jsonassert.JSONCompareMode.*; +import static org.skyscreamer.jsonassert.JSONCompareMode.LENIENT; import org.hamcrest.Matchers; +import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; -import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.containing; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import com.eclipsesource.json.JsonArray; import com.eclipsesource.json.JsonObject; @@ -198,6 +211,114 @@ public String getJSON() { } } + /** + * Unit test for {@link BoxFolder#getAllMetadata(String...)}. + */ + @Test + @Category(UnitTest.class) + public void testGetAllMetadataSendsCorrectRequest() { + final BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/folders/5010739061/metadata?fields=name%2Csize&limit=100", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"entries\": []}"; + } + }; + } + }); + + BoxFolder folder = new BoxFolder(api, "5010739061"); + Iterator iterator = folder.getAllMetadata("name", "size").iterator(); + iterator.hasNext(); + } + + /** + * Unit test for {@link BoxFolder#getAllMetadata(String...)}. + */ + @Test + @Category(UnitTest.class) + public void testGetAllMetadateParseAllFieldsCorrectly() { + final String firstEntrycurrentDocumentStage = "Init"; + final String firstEntryType = "documentFlow-452b4c9d-c3ad-4ac7-b1ad-9d5192f2fc5f"; + final String firstEntryParent = "file_5010739061"; + final String firstEntryID = "50ba0dba-0f89-4395-b867-3e057c1f6ed9"; + final int firstEntryVersion = 4; + final int firstEntryTypeVersion = 2; + final String firstEntryNeedApprovalFrom = "Smith"; + final String firstEntryTemplate = "documentFlow"; + final String firstEntryScope = "enterprise_12345"; + final String secondEntryType = "productInfo-9d7b6993-b09e-4e52-b197-e42f0ea995b9"; + final String secondEntryParent = "file_5010739061"; + final String secondEntryID = "15d1014a-06c2-47ad-9916-014eab456194"; + final int secondEntryVersion = 2; + final int secondEntryTypeVersion = 1; + final int secondEntrySkuNumber = 45334223; + final String secondEntryDescription = "Watch"; + final String secondEntryTemplate = "productInfo"; + final String secondEntryScope = "enterprise_12345"; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"entries\": [\n" + + " {\n" + + " \"currentDocumentStage\": \"Init\",\n" + + " \"$type\": \"documentFlow-452b4c9d-c3ad-4ac7-b1ad-9d5192f2fc5f\",\n" + + " \"$parent\": \"file_5010739061\",\n" + + " \"$id\": \"50ba0dba-0f89-4395-b867-3e057c1f6ed9\",\n" + + " \"$version\": 4,\n" + + " \"$typeVersion\": 2,\n" + + " \"needsApprovalFrom\": \"Smith\",\n" + + " \"$template\": \"documentFlow\",\n" + + " \"$scope\": \"enterprise_12345\"\n" + + " },\n" + + " {\n" + + " \"$type\": \"productInfo-9d7b6993-b09e-4e52-b197-e42f0ea995b9\",\n" + + " \"$parent\": \"file_5010739061\",\n" + + " \"$id\": \"15d1014a-06c2-47ad-9916-014eab456194\",\n" + + " \"$version\": 2,\n" + + " \"$typeVersion\": 1,\n" + + " \"skuNumber\": 45334223,\n" + + " \"description\": \"Watch\",\n" + + " \"$template\": \"productInfo\",\n" + + " \"$scope\": \"enterprise_12345\"\n" + + " }\n" + + "\n" + + " ],\n" + + " \"limit\": 100\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxFolder folder = new BoxFolder(api, "0"); + Iterator iterator = folder.getAllMetadata().iterator(); + Metadata entry = iterator.next(); + Assert.assertEquals(firstEntrycurrentDocumentStage, entry.get("/currentDocumentStage")); + Assert.assertEquals(firstEntryType, entry.getTypeName()); + Assert.assertEquals(firstEntryParent, entry.getParentID()); + Assert.assertEquals(firstEntryID, entry.getID()); + Assert.assertEquals(firstEntryVersion, (int) Integer.valueOf(entry.get("/$version"))); + Assert.assertEquals(firstEntryTypeVersion, (int) Integer.valueOf(entry.get("/$typeVersion"))); + Assert.assertEquals(firstEntryNeedApprovalFrom, entry.get("/needsApprovalFrom")); + Assert.assertEquals(firstEntryTemplate, entry.getTemplateName()); + Assert.assertEquals(firstEntryScope, entry.getScope()); + entry = iterator.next(); + Assert.assertEquals(secondEntryType, entry.getTypeName()); + Assert.assertEquals(secondEntryParent, entry.getParentID()); + Assert.assertEquals(secondEntryID, entry.getID()); + Assert.assertEquals(secondEntryVersion, (int) Integer.valueOf(entry.get("/$version"))); + Assert.assertEquals(secondEntryTypeVersion, (int) Integer.valueOf(entry.get("/$typeVersion"))); + Assert.assertEquals(secondEntrySkuNumber, (int) Integer.valueOf(entry.get("/skuNumber"))); + Assert.assertEquals(secondEntryDescription, entry.get("/description")); + Assert.assertEquals(secondEntryTemplate, entry.getTemplateName()); + Assert.assertEquals(secondEntryScope, entry.getScope()); + + } + @Test @Category(IntegrationTest.class) public void creatingAndDeletingFolderSucceeds() { From 7fd56819c13d1aba31ebdf1cdcd6175ec6a14838 Mon Sep 17 00:00:00 2001 From: gleb-urvanov Date: Wed, 16 Nov 2016 11:28:26 +0100 Subject: [PATCH 011/119] watermarking features implemented, tested, docimented --- README.md | 1 + doc/watermarking.md | 87 ++++++++++++ src/main/java/com/box/sdk/BoxFile.java | 36 +++++ src/main/java/com/box/sdk/BoxFolder.java | 26 ++++ src/main/java/com/box/sdk/BoxItem.java | 55 +++++++ src/main/java/com/box/sdk/BoxWatermark.java | 110 ++++++++++++++ src/test/java/com/box/sdk/BoxFileTest.java | 140 +++++++++++++++++- src/test/java/com/box/sdk/BoxFolderTest.java | 142 ++++++++++++++++++- 8 files changed, 593 insertions(+), 4 deletions(-) create mode 100644 doc/watermarking.md create mode 100644 src/main/java/com/box/sdk/BoxWatermark.java diff --git a/README.md b/README.md index 79b201c6f..64a427031 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,7 @@ You can find guides and tutorials in the `doc` directory. * [Collections](doc/collections.md) * [Devices](doc/devices.md) * [Retention Policies](doc/retention_policies.md) +* [Watermarking](doc/watermarking.md) Javadocs are generated when `gradle javadoc` is run and can be found in `build/doc/javadoc`. diff --git a/doc/watermarking.md b/doc/watermarking.md new file mode 100644 index 000000000..572bde70c --- /dev/null +++ b/doc/watermarking.md @@ -0,0 +1,87 @@ +Watermarking +====== + +The ability to watermark files and folders is represented as a sub-resource on the Files and Folders resources, respectively. You can think of the sub-resource as a "label" marking whether the file or folder is watermarked or not. If you apply a watermark label to a folder, then all files inside of it will be protected by the watermark (e.g. previews will be watermarked). However, those files' watermark sub-resource is independent from the folder that got watermarked. This allows you to watermark files and folders independently. + +* [Get Watermark on File](#get-watermark-on-file) +* [Apply Watermark on File](#apply-watermark-on-file) +* [Remove Watermark on File](#remove-watermark-on-file) +* [Get Watermark on Folder](#get-watermark-on-folder) +* [Apply Watermark on Folder](#apply-watermark-on-folder) +* [Remove Watermark on Folder](#remove-watermark-on-folder) + +Get Watermark on File +-------------- + +Calling [`getWatermark(String...)`][get-watermark-on-file] will return a BoxWatermark object containing information about the watermark associated for this file. If the file does not have a watermark applied on it, a 404 Not Found will be returned. + +```java +BoxFile file = new BoxFile(api, id); +BoxWatermark watermark = file.getWatermark(); +``` + +[get-watermark-on-file]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFile.html#getWatermark(java.lang.String...) + +Apply Watermark on File +-------------- + +To apply watermark on file, call [`applyWatermark()`][apply-watermark-on-file] method. While the endpoint accepts a JSON body describing the watermark to apply, custom watermarks are not supported yet. +The method will return a BoxWatermark object containing information about the watermark applied on this file. + +```java +BoxFile file = new BoxFile(api, id); +file.applyWatermark(); +``` + +[apply-watermark-on-file]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFile.html#applyWatermark() + +Remove Watermark on File +-------------- + +A watermark can be removed by calling the [`removeWatermark()`][remove-watermark-on-file] method. +If the file did not have a watermark applied on it, a 404 Not Found will be returned by API. + +```java +BoxFile file = new BoxFile(api, id); +file.removeWatermark(); +``` + +[remove-watermark-on-file]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFile.html#removeWatermark() + +Get Watermark on Folder +-------------- + +Calling [`getWatermark(String...)`][get-watermark-on-folder] will return a BoxWatermark object containing information about the watermark associated for this folder. If the folder does not have a watermark applied on it, a 404 Not Found will be returned. + +```java +BoxFolder folder = new BoxFolder(api, id); +BoxWatermark watermark = folder.getWatermark(); +``` + +[get-watermark-on-folder]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFolder.html#getWatermark(java.lang.String...) + +Apply Watermark on Folder +-------------- + +To apply watermark on folder, call [`applyWatermark()`][apply-watermark-on-folder] method. While the endpoint accepts a JSON body describing the watermark to apply, custom watermarks are not supported yet. +The method will return a BoxWatermark object containing information about the watermark applied on this folder. + +```java +BoxFolder folder = new BoxFolder(api, id); +fodler.applyWatermark(); +``` + +[apply-watermark-on-folder]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFolder.html#applyWatermark() + +Remove Watermark on Folder +-------------- + +A watermark can be removed by calling the [`removeWatermark()`][remove-watermark-on-folder] method. +If the folder did not have a watermark applied on it, a 404 Not Found will be returned by API. + +```java +BoxFolder folder = new BoxFolder(api, id); +folder.removeWatermark(); +``` + +[remove-watermark-on-folder]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFolder.html#removeWatermark() diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 8530ac0ae..794b84398 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -758,6 +758,42 @@ public void deleteMetadata(String typeName, String scope) { request.send(); } + /** + * Used to retrieve the watermark for the file. + * If the file does not have a watermark applied to it, a 404 Not Found will be returned by API. + * @param fields the fields to retrieve. + * @return the watermark associated with the file. + */ + public BoxWatermark getWatermark(String... fields) { + return this.getWatermark(FILE_URL_TEMPLATE, fields); + } + + /** + * Used to apply or update the watermark for the file. + * @return the watermark associated with the file. + */ + public BoxWatermark applyWatermark() { + return this.applyWatermark(FILE_URL_TEMPLATE, BoxWatermark.WATERMARK_DEFAULT_IMPRINT); + } + + /** + * Removes a watermark from the file. + * If the file did not have a watermark applied to it, a 404 Not Found will be returned by API. + */ + public void removeWatermark() { + this.removeWatermark(FILE_URL_TEMPLATE); + } + + private String scopeBasedOnType(String typeName) { + String scope; + if (typeName.equals(Metadata.DEFAULT_METADATA_TYPE)) { + scope = Metadata.GLOBAL_METADATA_SCOPE; + } else { + scope = Metadata.ENTERPRISE_METADATA_SCOPE; + } + return scope; + } + @Override public BoxFile.Info setCollections(BoxCollection... collections) { JsonArray jsonArray = new JsonArray(); diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index 8d8c70010..592f96e25 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -532,6 +532,32 @@ public BoxWebHook.Info addWebHook(URL address, BoxWebHook.Trigger... triggers) { return BoxWebHook.create(this, address, triggers); } + /** + * Used to retrieve the watermark for the folder. + * If the folder does not have a watermark applied to it, a 404 Not Found will be returned by API. + * @param fields the fields to retrieve. + * @return the watermark associated with the folder. + */ + public BoxWatermark getWatermark(String... fields) { + return this.getWatermark(FOLDER_INFO_URL_TEMPLATE, fields); + } + + /** + * Used to apply or update the watermark for the folder. + * @return the watermark associated with the folder. + */ + public BoxWatermark applyWatermark() { + return this.applyWatermark(FOLDER_INFO_URL_TEMPLATE, BoxWatermark.WATERMARK_DEFAULT_IMPRINT); + } + + /** + * Removes a watermark from the folder. + * If the folder did not have a watermark applied to it, a 404 Not Found will be returned by API. + */ + public void removeWatermark() { + this.removeWatermark(FOLDER_INFO_URL_TEMPLATE); + } + /** * This method is deprecated, please use the {@link BoxSearch} class instead. * Searches this folder and all descendant folders using a given queryPlease use BoxSearch Instead. diff --git a/src/main/java/com/box/sdk/BoxItem.java b/src/main/java/com/box/sdk/BoxItem.java index 7248b1a32..d9c151fa1 100644 --- a/src/main/java/com/box/sdk/BoxItem.java +++ b/src/main/java/com/box/sdk/BoxItem.java @@ -28,6 +28,11 @@ public abstract class BoxItem extends BoxResource { private static final URLTemplate SHARED_ITEM_URL_TEMPLATE = new URLTemplate("shared_items"); + /** + * Url template for operations with watermarks. + */ + private static final URLTemplate WATERMARK_URL_TEMPLATE = new URLTemplate("/watermark"); + /** * Constructs a BoxItem for an item with a given ID. * @param api the API connection to be used by the item. @@ -63,6 +68,56 @@ public static BoxItem.Info getSharedItem(BoxAPIConnection api, String sharedLink return (BoxItem.Info) BoxResource.parseInfo(newAPI, json); } + /** + * Used to retrieve the watermark for the item. + * If the item does not have a watermark applied to it, a 404 Not Found will be returned by API. + * @param itemUrl url template for the item. + * @param fields the fields to retrieve. + * @return the watermark associated with the item. + */ + protected BoxWatermark getWatermark(URLTemplate itemUrl, String... fields) { + URL watermarkUrl = itemUrl.build(this.getAPI().getBaseURL(), this.getID()); + QueryStringBuilder builder = new QueryStringBuilder(); + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + URL url = WATERMARK_URL_TEMPLATE.buildWithQuery(watermarkUrl.toString(), builder.toString()); + BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + return new BoxWatermark(response.getJSON()); + } + + /** + * Used to apply or update the watermark for the item. + * @param itemUrl url template for the item. + * @param imprint the value must be "default", as custom watermarks is not yet supported. + * @return the watermark associated with the item. + */ + protected BoxWatermark applyWatermark(URLTemplate itemUrl, String imprint) { + URL watermarkUrl = itemUrl.build(this.getAPI().getBaseURL(), this.getID()); + URL url = WATERMARK_URL_TEMPLATE.build(watermarkUrl.toString()); + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT"); + JsonObject body = new JsonObject() + .add(BoxWatermark.WATERMARK_JSON_KEY, new JsonObject() + .add(BoxWatermark.WATERMARK_IMPRINT_JSON_KEY, imprint)); + request.setBody(body.toString()); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + return new BoxWatermark(response.getJSON()); + } + + /** + * Removes a watermark from the item. + * If the item did not have a watermark applied to it, a 404 Not Found will be returned by API. + * @param itemUrl url template for the item. + */ + protected void removeWatermark(URLTemplate itemUrl) { + URL watermarkUrl = itemUrl.build(this.getAPI().getBaseURL(), this.getID()); + URL url = WATERMARK_URL_TEMPLATE.build(watermarkUrl.toString()); + BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE"); + BoxAPIResponse response = request.send(); + response.disconnect(); + } + /** * Copies this item to another folder. * @param destination the destination folder. diff --git a/src/main/java/com/box/sdk/BoxWatermark.java b/src/main/java/com/box/sdk/BoxWatermark.java new file mode 100644 index 000000000..38fd7f88c --- /dev/null +++ b/src/main/java/com/box/sdk/BoxWatermark.java @@ -0,0 +1,110 @@ +package com.box.sdk; + +import java.text.ParseException; +import java.util.Date; + +import com.eclipsesource.json.JsonObject; +import com.eclipsesource.json.JsonValue; + +/** + * Represents a watermark. + * Watermarks are used to protect sensitive information in the Box account. + * + * @see Watermarking + */ +public class BoxWatermark extends BoxJSONObject { + + /** + * Default imprint for watermarks. + */ + public static final String WATERMARK_DEFAULT_IMPRINT = "default"; + + /** + * Json key for watermark. + * @see BoxWatermark#parseJSONMember(JsonObject.Member) + */ + public static final String WATERMARK_JSON_KEY = "watermark"; + + /** + * Json key for created_at param. + * @see BoxWatermark#parseJSONMember(JsonObject.Member) + */ + public static final String CREATED_AT_JSON_KEY = "created_at"; + + /** + * Json key for modified_at param. + * @see BoxWatermark#parseJSONMember(JsonObject.Member) + */ + public static final String MODIFIED_AT_JSON_KEY = "modified_at"; + + /** + * Json key for watermark param. + */ + public static final String WATERMARK_IMPRINT_JSON_KEY = "imprint"; + + /** + * @see #getCreatedAt() + */ + private Date createdAt; + + /** + * @see #getModifiedAt() + */ + private Date modifiedAt; + + /** + * Constructs an empty watermark object. + */ + public BoxWatermark() { + super(); + } + + /** + * Constructs a watermark object by parsing information from a JSON string. + * @param json the JSON string to parse. + */ + public BoxWatermark(String json) { + super(json); + } + + /** + * Constructs a watermark object using an already parsed JSON object. + * @param jsonObject the parsed JSON object. + */ + BoxWatermark(JsonObject jsonObject) { + super(jsonObject); + } + + /** + * @return the time that the watermark was created. + */ + public Date getCreatedAt() { + return this.createdAt; + } + + /** + * @return the time that the watermark was last modified. + */ + public Date getModifiedAt() { + return this.modifiedAt; + } + + /** + * {@inheritDoc} + */ + @Override + void parseJSONMember(JsonObject.Member member) { + super.parseJSONMember(member); + String memberName = member.getName(); + JsonValue value = member.getValue(); + if (memberName.equals(WATERMARK_JSON_KEY)) { + try { + this.createdAt = BoxDateFormat.parse(value.asObject().get(CREATED_AT_JSON_KEY).asString()); + this.modifiedAt = BoxDateFormat.parse(value.asObject().get(MODIFIED_AT_JSON_KEY).asString()); + } catch (ParseException e) { + assert false : "A ParseException indicates a bug in the SDK."; + } + } + } + +} diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index 85a039286..d8ad78056 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -10,13 +10,22 @@ import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Date; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isEmptyOrNullString; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.anyLong; @@ -30,7 +39,136 @@ import org.junit.Test; import org.junit.experimental.categories.Category; +import com.eclipsesource.json.JsonObject; + +/** + * {@link BoxFile} related tests. + */ public class BoxFileTest { + + /** + * Unit test for {@link BoxFile#getWatermark(String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetWatermarkSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/files/0/watermark", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{}"; + } + }; + } + }); + + new BoxFile(api, "0").getWatermark(); + } + + /** + * Unit test for {@link BoxFile#getWatermark(String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetWatermarkParseAllFieldsCorrectly() throws ParseException { + final Date createdAt = BoxDateFormat.parse("2016-10-31T15:33:33-07:00"); + final Date modifiedAt = BoxDateFormat.parse("2016-11-31T15:33:33-07:00"); + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"watermark\": {\n" + + " \"created_at\": \"2016-10-31T15:33:33-07:00\",\n" + + " \"modified_at\": \"2016-11-31T15:33:33-07:00\"\n" + + " }\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxWatermark watermark = new BoxFile(api, "0").getWatermark(); + Assert.assertEquals(createdAt, watermark.getCreatedAt()); + Assert.assertEquals(modifiedAt, watermark.getModifiedAt()); + } + + /** + * Unit test for {@link BoxFile#applyWatermark()} + */ + @Test + @Category(UnitTest.class) + public void testApplyWatermarkSendsCorrectJson() { + final String imprint = "default"; + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals("https://api.box.com/2.0/files/0/watermark", + request.getUrl().toString()); + Assert.assertEquals(imprint, json.get("watermark").asObject().get("imprint").asString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{}"; + } + }; + } + }); + + new BoxFile(api, "0").applyWatermark(); + } + + /** + * Unit test for {@link BoxFile#applyWatermark()} + */ + @Test + @Category(UnitTest.class) + public void testApplyWatermarkParseAllFieldsCorrectly() throws ParseException { + final Date createdAt = BoxDateFormat.parse("2016-10-31T15:33:33-07:00"); + final Date modifiedAt = BoxDateFormat.parse("2016-11-31T15:33:33-07:00"); + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"watermark\": {\n" + + " \"created_at\": \"2016-10-31T15:33:33-07:00\",\n" + + " \"modified_at\": \"2016-11-31T15:33:33-07:00\"\n" + + " }\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxWatermark watermark = new BoxFile(api, "0").applyWatermark(); + Assert.assertEquals(createdAt, watermark.getCreatedAt()); + Assert.assertEquals(modifiedAt, watermark.getModifiedAt()); + } + + /** + * Unit test for {@link BoxFile#removeWatermark()} + */ + @Test + @Category(UnitTest.class) + public void testRemoveWatermarkSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/files/0/watermark", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{}"; + } + }; + } + }); + + new BoxFile(api, "0").removeWatermark(); + } + @Test @Category(IntegrationTest.class) public void uploadAndDownloadFileSucceeds() throws IOException { diff --git a/src/test/java/com/box/sdk/BoxFolderTest.java b/src/test/java/com/box/sdk/BoxFolderTest.java index 1085216ab..5a550ac5f 100644 --- a/src/test/java/com/box/sdk/BoxFolderTest.java +++ b/src/test/java/com/box/sdk/BoxFolderTest.java @@ -4,8 +4,13 @@ import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; +import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.*; +import java.util.Collection; +import java.util.Date; +import java.util.EnumSet; +import java.util.List; +import java.util.TimeZone; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; @@ -19,14 +24,22 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.skyscreamer.jsonassert.JSONCompareMode.*; +import static org.skyscreamer.jsonassert.JSONCompareMode.LENIENT; import org.hamcrest.Matchers; +import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; -import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.containing; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import com.eclipsesource.json.JsonArray; import com.eclipsesource.json.JsonObject; @@ -198,6 +211,129 @@ public String getJSON() { } } + /** + * Unit test for {@link BoxFolder#getWatermark(String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetWatermarkSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/folders/0/watermark", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{}"; + } + }; + } + }); + + new BoxFolder(api, "0").getWatermark(); + } + + /** + * Unit test for {@link BoxFolder#getWatermark(String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetWatermarkParseAllFieldsCorrectly() throws ParseException { + final Date createdAt = BoxDateFormat.parse("2016-10-31T15:33:33-07:00"); + final Date modifiedAt = BoxDateFormat.parse("2016-11-31T15:33:33-07:00"); + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"watermark\": {\n" + + " \"created_at\": \"2016-10-31T15:33:33-07:00\",\n" + + " \"modified_at\": \"2016-11-31T15:33:33-07:00\"\n" + + " }\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxWatermark watermark = new BoxFolder(api, "0").getWatermark(); + Assert.assertEquals(createdAt, watermark.getCreatedAt()); + Assert.assertEquals(modifiedAt, watermark.getModifiedAt()); + } + + /** + * Unit test for {@link BoxFolder#applyWatermark()} + */ + @Test + @Category(UnitTest.class) + public void testApplyWatermarkSendsCorrectJson() { + final String imprint = "default"; + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals("https://api.box.com/2.0/folders/0/watermark", + request.getUrl().toString()); + Assert.assertEquals(imprint, json.get("watermark").asObject().get("imprint").asString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{}"; + } + }; + } + }); + + new BoxFolder(api, "0").applyWatermark(); + } + + /** + * Unit test for {@link BoxFolder#applyWatermark()} + */ + @Test + @Category(UnitTest.class) + public void testApplyWatermarkParseAllFieldsCorrectly() throws ParseException { + final Date createdAt = BoxDateFormat.parse("2016-10-31T15:33:33-07:00"); + final Date modifiedAt = BoxDateFormat.parse("2016-11-31T15:33:33-07:00"); + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"watermark\": {\n" + + " \"created_at\": \"2016-10-31T15:33:33-07:00\",\n" + + " \"modified_at\": \"2016-11-31T15:33:33-07:00\"\n" + + " }\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxWatermark watermark = new BoxFolder(api, "0").applyWatermark(); + Assert.assertEquals(createdAt, watermark.getCreatedAt()); + Assert.assertEquals(modifiedAt, watermark.getModifiedAt()); + } + + /** + * Unit test for {@link BoxFolder#removeWatermark()} + */ + @Test + @Category(UnitTest.class) + public void testRemoveWatermarkSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/folders/0/watermark", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{}"; + } + }; + } + }); + + new BoxFolder(api, "0").removeWatermark(); + } + @Test @Category(IntegrationTest.class) public void creatingAndDeletingFolderSucceeds() { From f0b453fa20fb88ae2fe7960eeb55fd9500051eb4 Mon Sep 17 00:00:00 2001 From: gleb-urvanov Date: Wed, 16 Nov 2016 16:38:30 +0100 Subject: [PATCH 012/119] fixes and tests for tasks --- src/main/java/com/box/sdk/BoxTask.java | 64 +++ .../java/com/box/sdk/BoxTaskAssignment.java | 18 + .../box/sdk/BoxTaskAssignmentIterator.java | 67 +++ src/test/java/com/box/sdk/BoxFileTest.java | 127 ++++- .../com/box/sdk/BoxTaskAssignmentTest.java | 278 +++++++++ src/test/java/com/box/sdk/BoxTaskTest.java | 529 ++++++++++++++++++ 6 files changed, 1082 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/box/sdk/BoxTaskAssignmentIterator.java create mode 100644 src/test/java/com/box/sdk/BoxTaskAssignmentTest.java diff --git a/src/main/java/com/box/sdk/BoxTask.java b/src/main/java/com/box/sdk/BoxTask.java index 6139d0254..115a50ce2 100644 --- a/src/main/java/com/box/sdk/BoxTask.java +++ b/src/main/java/com/box/sdk/BoxTask.java @@ -4,6 +4,7 @@ import java.text.ParseException; import java.util.ArrayList; import java.util.Date; +import java.util.Iterator; import java.util.List; import com.eclipsesource.json.JsonArray; @@ -66,6 +67,33 @@ public BoxTaskAssignment.Info addAssignment(BoxUser assignTo) { return addedAssignment.new Info(responseJSON); } + /** + * Adds a new assignment to this task using user's login as identifier. + * @param assignToLogin the login of user to assign the task to. + * @return information about the newly added task assignment. + */ + public BoxTaskAssignment.Info addAssignmentByLogin(String assignToLogin) { + JsonObject taskJSON = new JsonObject(); + taskJSON.add("type", "task"); + taskJSON.add("id", this.getID()); + + JsonObject assignToJSON = new JsonObject(); + assignToJSON.add("login", assignToLogin); + + JsonObject requestJSON = new JsonObject(); + requestJSON.add("task", taskJSON); + requestJSON.add("assign_to", assignToJSON); + + URL url = ADD_TASK_ASSIGNMENT_URL_TEMPLATE.build(this.getAPI().getBaseURL()); + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); + request.setBody(requestJSON.toString()); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); + + BoxTaskAssignment addedAssignment = new BoxTaskAssignment(this.getAPI(), responseJSON.get("id").asString()); + return addedAssignment.new Info(responseJSON); + } + /** * Gets any assignments for this task. * @return a list of assignments for this task. @@ -89,6 +117,25 @@ public List getAssignments() { return assignments; } + /** + * Gets an iterable of all the assignments of this task. + * @param fields the fields to retrieve. + * @return an iterable containing info about all the assignments. + */ + public Iterable getAllAssignments(String ... fields) { + final QueryStringBuilder builder = new QueryStringBuilder(); + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + return new Iterable() { + public Iterator iterator() { + URL url = GET_ASSIGNMENTS_URL_TEMPLATE.buildWithQuery( + BoxTask.this.getAPI().getBaseURL(), builder.toString(), BoxTask.this.getID()); + return new BoxTaskAssignmentIterator(BoxTask.this.getAPI(), url); + } + }; + } + /** * Gets information about this task. * @return info about this task. @@ -101,6 +148,23 @@ public Info getInfo() { return new Info(responseJSON); } + /** + * Gets information about this task. + * @param fields the fields to retrieve. + * @return info about this task. + */ + public Info getInfo(String... fields) { + QueryStringBuilder builder = new QueryStringBuilder(); + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + URL url = TASK_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), builder.toString(), this.getID()); + BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); + return new Info(responseJSON); + } + /** * Updates the information about this task with any info fields that have been modified locally. * diff --git a/src/main/java/com/box/sdk/BoxTaskAssignment.java b/src/main/java/com/box/sdk/BoxTaskAssignment.java index 817d4e9a5..46f354e86 100644 --- a/src/main/java/com/box/sdk/BoxTaskAssignment.java +++ b/src/main/java/com/box/sdk/BoxTaskAssignment.java @@ -48,6 +48,24 @@ public Info getInfo() { return new Info(responseJSON); } + /** + * Gets information about this task assignment. + * @param fields the fields to retrieve. + * @return info about this task assignment. + */ + public Info getInfo(String... fields) { + QueryStringBuilder builder = new QueryStringBuilder(); + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + URL url = TASK_ASSIGNMENT_URL_TEMPLATE.buildWithQuery( + this.getAPI().getBaseURL(), builder.toString(), this.getID()); + BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); + return new Info(responseJSON); + } + /** * Updates the information about this task assignment with any info fields that have been modified locally. * diff --git a/src/main/java/com/box/sdk/BoxTaskAssignmentIterator.java b/src/main/java/com/box/sdk/BoxTaskAssignmentIterator.java new file mode 100644 index 000000000..2dd49f813 --- /dev/null +++ b/src/main/java/com/box/sdk/BoxTaskAssignmentIterator.java @@ -0,0 +1,67 @@ +package com.box.sdk; + +import java.net.URL; +import java.util.Iterator; + +import com.eclipsesource.json.JsonObject; + + +/** + * An iterator object for {@link BoxTaskAssignment} object. + * Supports offset-based paging. + */ +class BoxTaskAssignmentIterator implements Iterator { + + /** + * The limit of entries per response. + */ + private static final long LIMIT = 100; + + /** + * The API connection to be used. + */ + private final BoxAPIConnection api; + + /** + * Iterator object with paging support. + */ + private final JSONIterator jsonIterator; + + /** + * Creates new BoxTaskAssignment iterator. + * @param api The API connection to be used by the iterator. + * @param url The endpoint url. + */ + BoxTaskAssignmentIterator(BoxAPIConnection api, URL url) { + this.api = api; + this.jsonIterator = new JSONIterator(api, url, LIMIT); + } + + /** + * @return false if current element is the last. + */ + @Override + public boolean hasNext() { + return this.jsonIterator.hasNext(); + } + + + /** + * @return next BoxTaskAssignment.Info object in the list. + */ + @Override + public BoxTaskAssignment.Info next() { + JsonObject nextJSONObject = this.jsonIterator.next(); + String id = nextJSONObject.get("id").asString(); + + BoxTaskAssignment assignment = new BoxTaskAssignment(this.api, id); + return assignment.new Info(nextJSONObject); + } + + /** + * Remove operation is not supported. + */ + public void remove() { + throw new UnsupportedOperationException(); + } +} diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index 85a039286..1307f994d 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -10,13 +10,22 @@ import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Date; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isEmptyOrNullString; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.anyLong; @@ -30,7 +39,123 @@ import org.junit.Test; import org.junit.experimental.categories.Category; +import com.eclipsesource.json.JsonObject; + +/** + * {@link BoxFile} related tests. + */ public class BoxFileTest { + + /** + * Unit test for {@link BoxFile#addTask(BoxTask.Action, String, Date)} + */ + @Test + @Category(UnitTest.class) + public void testAddTaskSendsCorrectJson() throws ParseException { + final String itemType = "file"; + final String itemID = "1"; + final String action = "review"; + final String message = "text message"; + final Date dueAt = BoxDateFormat.parse("2016-05-09T17:41:27-07:00"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals("https://api.box.com/2.0/tasks", + request.getUrl().toString()); + Assert.assertEquals(itemID, json.get("item").asObject().get("id").asString()); + Assert.assertEquals(itemType, json.get("item").asObject().get("type").asString()); + Assert.assertEquals(action, json.get("action").asString()); + Assert.assertEquals(message, json.get("message").asString()); + try { + Assert.assertEquals(dueAt, BoxDateFormat.parse(json.get("due_at").asString())); + } catch (ParseException e) { + assert false; + } + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + new BoxFile(api, "1").addTask(BoxTask.Action.REVIEW, message, dueAt); + } + + /** + * Unit test for {@link BoxFile#addTask(BoxTask.Action, String, Date)} + */ + @Test + @Category(UnitTest.class) + public void testAddTaskParseAllFieldsCorrectly() throws ParseException { + final String id = "1839355"; + final String itemID = "7287087200"; + final String itemSequenceID = "0"; + final String itemEtag = "0"; + final String itemSha1 = "0bbd79a105c504f99573e3799756debba4c760cd"; + final String itemName = "box-logo.png"; + final Date dueAt = BoxDateFormat.parse("2014-04-03T11:09:43-07:00"); + final BoxTask.Action action = BoxTask.Action.REVIEW; + final String message = "REVIEW PLZ K THX"; + final int assignmentCount = 0; + final boolean isCompleted = false; + final String createdByID = "11993747"; + final String createdByName = "sean"; + final String createdByLogin = "sean@box.com"; + final Date createdAt = BoxDateFormat.parse("2013-04-03T11:12:54-07:00"); + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"task\",\n" + + " \"id\": \"1839355\",\n" + + " \"item\": {\n" + + " \"type\": \"file\",\n" + + " \"id\": \"7287087200\",\n" + + " \"sequence_id\": \"0\",\n" + + " \"etag\": \"0\",\n" + + " \"sha1\": \"0bbd79a105c504f99573e3799756debba4c760cd\",\n" + + " \"name\": \"box-logo.png\"\n" + + " },\n" + + " \"due_at\": \"2014-04-03T11:09:43-07:00\",\n" + + " \"action\": \"review\",\n" + + " \"message\": \"REVIEW PLZ K THX\",\n" + + " \"task_assignment_collection\": {\n" + + " \"total_count\": 0,\n" + + " \"entries\": []\n" + + " },\n" + + " \"is_completed\": false,\n" + + " \"created_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"11993747\",\n" + + " \"name\": \"sean\",\n" + + " \"login\": \"sean@box.com\"\n" + + " },\n" + + " \"created_at\": \"2013-04-03T11:12:54-07:00\"\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxTask.Info info = new BoxFile(api, id).addTask(action, message, dueAt); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(itemID, info.getItem().getID()); + Assert.assertEquals(itemSequenceID, info.getItem().getSequenceID()); + Assert.assertEquals(itemEtag, info.getItem().getEtag()); + Assert.assertEquals(itemSha1, info.getItem().getSha1()); + Assert.assertEquals(itemName, info.getItem().getName()); + Assert.assertEquals(dueAt, info.getDueAt()); + Assert.assertEquals(action, info.getAction()); + Assert.assertEquals(message, info.getMessage()); + Assert.assertEquals(assignmentCount, info.getTaskAssignments().size()); + Assert.assertEquals(isCompleted, info.isCompleted()); + Assert.assertEquals(createdByID, info.getCreatedBy().getID()); + Assert.assertEquals(createdByName, info.getCreatedBy().getName()); + Assert.assertEquals(createdByLogin, info.getCreatedBy().getLogin()); + Assert.assertEquals(createdAt, info.getCreatedAt()); + } + @Test @Category(IntegrationTest.class) public void uploadAndDownloadFileSucceeds() throws IOException { diff --git a/src/test/java/com/box/sdk/BoxTaskAssignmentTest.java b/src/test/java/com/box/sdk/BoxTaskAssignmentTest.java new file mode 100644 index 000000000..8be7c1f24 --- /dev/null +++ b/src/test/java/com/box/sdk/BoxTaskAssignmentTest.java @@ -0,0 +1,278 @@ +package com.box.sdk; + +import java.text.ParseException; +import java.util.Date; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import com.eclipsesource.json.JsonObject; + +/** + * {@link BoxTaskAssignment} related tests. + */ +public class BoxTaskAssignmentTest { + + /** + * Unit test for {@link BoxTaskAssignment#getInfo()} + */ + @Test + @Category(UnitTest.class) + public void testGetInfoSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/task_assignments/0", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + new BoxTaskAssignment(api, "0").getInfo(); + } + + /** + * Unit test for {@link BoxTaskAssignment#getInfo(String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetInfoSendsCorrectRequestWithFields() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/task_assignments/0?fields=item%2Cmessage", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + new BoxTaskAssignment(api, "0").getInfo("item", "message"); + } + + /** + * Unit test for {@link BoxTaskAssignment#getInfo(String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetInfoParseAllFieldsCorrectly() throws ParseException { + final String id = "2698512"; + final String itemID = "8018809384"; + final String itemSequenceID = "0"; + final String itemEtag = "0"; + final String itemSha1 = "7840095ee096ee8297676a138d4e316eabb3ec96"; + final String itemName = "scrumworksToTrello.js"; + final String assignedToID = "1992432"; + final String assignedToName = "rhaegar@box.com"; + final String assignedToLogin = "rhaegar@box.com"; + final String message = null; + final Date completedAt = null; + final Date assignedAt = BoxDateFormat.parse("2013-05-10T11:43:41-07:00"); + final Date remindedAt = null; + final BoxTaskAssignment.ResolutionState resolutionState = BoxTaskAssignment.ResolutionState.INCOMPLETE; + final String assignedByID = "11993747"; + final String assignedByName = "sean"; + final String assignedByLogin = "sean@box.com"; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"task_assignment\",\n" + + " \"id\": \"2698512\",\n" + + " \"item\": {\n" + + " \"type\": \"file\",\n" + + " \"id\": \"8018809384\",\n" + + " \"sequence_id\": \"0\",\n" + + " \"etag\": \"0\",\n" + + " \"sha1\": \"7840095ee096ee8297676a138d4e316eabb3ec96\",\n" + + " \"name\": \"scrumworksToTrello.js\"\n" + + " },\n" + + " \"assigned_to\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"1992432\",\n" + + " \"name\": \"rhaegar@box.com\",\n" + + " \"login\": \"rhaegar@box.com\"\n" + + " },\n" + + " \"message\": null,\n" + + " \"completed_at\": null,\n" + + " \"assigned_at\": \"2013-05-10T11:43:41-07:00\",\n" + + " \"reminded_at\": null,\n" + + " \"resolution_state\": \"incomplete\",\n" + + " \"assigned_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"11993747\",\n" + + " \"name\": \"sean\",\n" + + " \"login\": \"sean@box.com\"\n" + + " }\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxTaskAssignment.Info info = new BoxTaskAssignment(api, id).getInfo("item", "message"); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(itemID, info.getItem().getID()); + Assert.assertEquals(itemSequenceID, info.getItem().getSequenceID()); + Assert.assertEquals(itemEtag, info.getItem().getEtag()); + Assert.assertEquals(itemSha1, ((BoxFile.Info) info.getItem()).getSha1()); + Assert.assertEquals(itemName, info.getItem().getName()); + Assert.assertEquals(assignedToID, info.getAssignedTo().getID()); + Assert.assertEquals(assignedToName, info.getAssignedTo().getName()); + Assert.assertEquals(assignedToLogin, info.getAssignedTo().getLogin()); + Assert.assertEquals(message, info.getMessage()); + Assert.assertEquals(completedAt, info.getCompletedAt()); + Assert.assertEquals(assignedAt, info.getAssignedAt()); + Assert.assertEquals(remindedAt, info.getRemindedAt()); + Assert.assertEquals(resolutionState, info.getResolutionState()); + Assert.assertEquals(assignedByID, info.getAssignedBy().getID()); + Assert.assertEquals(assignedByName, info.getAssignedBy().getName()); + Assert.assertEquals(assignedByLogin, info.getAssignedBy().getLogin()); + } + + /** + * Unit test for {@link BoxTaskAssignment#updateInfo(BoxTaskAssignment.Info)} + */ + @Test + @Category(UnitTest.class) + public void testUpdateInfoSendsCorrectJson() { + final String message = "hello!"; + final String resolutionState = "approved"; + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals("https://api.box.com/2.0/task_assignments/0", + request.getUrl().toString()); + Assert.assertEquals(message, json.get("message").asString()); + Assert.assertEquals(resolutionState, json.get("resolution_state").asString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + BoxTaskAssignment assignment = new BoxTaskAssignment(api, "0"); + BoxTaskAssignment.Info info = assignment.new Info(); + info.addPendingChange("message", message); + info.addPendingChange("resolution_state", resolutionState); + assignment.updateInfo(info); + } + + /** + * Unit test for {@link BoxTaskAssignment#updateInfo(BoxTaskAssignment.Info)} + */ + @Test + @Category(UnitTest.class) + public void testUpdateInfoParseAllFieldsCorrectly() throws ParseException { + final String id = "2698512"; + final String itemID = "8018809384"; + final String itemSequenceID = "0"; + final String itemEtag = "0"; + final String itemSha1 = "7840095ee096ee8297676a138d4e316eabb3ec96"; + final String itemName = "scrumworksToTrello.js"; + final String assignedToID = "1992432"; + final String assignedToName = "rhaegar@box.com"; + final String assignedToLogin = "rhaegar@box.com"; + final String message = "hello!!!"; + final Date completedAt = null; + final Date assignedAt = BoxDateFormat.parse("2013-05-10T11:43:41-07:00"); + final Date remindedAt = null; + final BoxTaskAssignment.ResolutionState resolutionState = BoxTaskAssignment.ResolutionState.INCOMPLETE; + final String assignedByID = "11993747"; + final String assignedByName = "sean"; + final String assignedByLogin = "sean@box.com"; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"task_assignment\",\n" + + " \"id\": \"2698512\",\n" + + " \"item\": {\n" + + " \"type\": \"file\",\n" + + " \"id\": \"8018809384\",\n" + + " \"sequence_id\": \"0\",\n" + + " \"etag\": \"0\",\n" + + " \"sha1\": \"7840095ee096ee8297676a138d4e316eabb3ec96\",\n" + + " \"name\": \"scrumworksToTrello.js\"\n" + + " },\n" + + " \"assigned_to\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"1992432\",\n" + + " \"name\": \"rhaegar@box.com\",\n" + + " \"login\": \"rhaegar@box.com\"\n" + + " },\n" + + " \"message\": \"hello!!!\",\n" + + " \"completed_at\": null,\n" + + " \"assigned_at\": \"2013-05-10T11:43:41-07:00\",\n" + + " \"reminded_at\": null,\n" + + " \"resolution_state\": \"incomplete\",\n" + + " \"assigned_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"11993747\",\n" + + " \"name\": \"sean\",\n" + + " \"login\": \"sean@box.com\"\n" + + " }\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxTaskAssignment assignment = new BoxTaskAssignment(api, id); + BoxTaskAssignment.Info info = assignment.new Info(); + info.addPendingChange("message", message); + assignment.updateInfo(info); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(itemID, info.getItem().getID()); + Assert.assertEquals(itemSequenceID, info.getItem().getSequenceID()); + Assert.assertEquals(itemEtag, info.getItem().getEtag()); + Assert.assertEquals(itemSha1, ((BoxFile.Info) info.getItem()).getSha1()); + Assert.assertEquals(itemName, info.getItem().getName()); + Assert.assertEquals(assignedToID, info.getAssignedTo().getID()); + Assert.assertEquals(assignedToName, info.getAssignedTo().getName()); + Assert.assertEquals(assignedToLogin, info.getAssignedTo().getLogin()); + Assert.assertEquals(message, info.getMessage()); + Assert.assertEquals(completedAt, info.getCompletedAt()); + Assert.assertEquals(assignedAt, info.getAssignedAt()); + Assert.assertEquals(remindedAt, info.getRemindedAt()); + Assert.assertEquals(resolutionState, info.getResolutionState()); + Assert.assertEquals(assignedByID, info.getAssignedBy().getID()); + Assert.assertEquals(assignedByName, info.getAssignedBy().getName()); + Assert.assertEquals(assignedByLogin, info.getAssignedBy().getLogin()); + } + + /** + * Unit test for {@link BoxTaskAssignment#updateInfo(BoxTaskAssignment.Info)} + */ + @Test + @Category(UnitTest.class) + public void testDeleteSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/task_assignments/0", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + new BoxTaskAssignment(api, "0").delete(); + } +} diff --git a/src/test/java/com/box/sdk/BoxTaskTest.java b/src/test/java/com/box/sdk/BoxTaskTest.java index 848c74938..a016b78d6 100644 --- a/src/test/java/com/box/sdk/BoxTaskTest.java +++ b/src/test/java/com/box/sdk/BoxTaskTest.java @@ -2,18 +2,547 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.text.ParseException; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; +import java.util.Iterator; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; +import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; +import com.eclipsesource.json.JsonObject; + +/** + * {@link BoxTask} related tests. + */ public class BoxTaskTest { + + /** + * Unit test for {@link BoxTask#getInfo()} + */ + @Test + @Category(UnitTest.class) + public void testGetInfoSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/tasks/0", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + new BoxTask(api, "0").getInfo(); + } + + /** + * Unit test for {@link BoxTask#getInfo(String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetInfoSendsCorrectRequestWithFields() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/tasks/0?fields=item%2Cdue_at", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + new BoxTask(api, "0").getInfo("item", "due_at"); + } + + /** + * Unit test for {@link BoxTask#getInfo()} + */ + @Test + @Category(UnitTest.class) + public void testGetInfoParseAllFieldsCorrectly() throws ParseException { + final String id = "1839355"; + final String itemID = "7287087200"; + final String itemSequenceID = "0"; + final String itemEtag = "0"; + final String itemSha1 = "0bbd79a105c504f99573e3799756debba4c760cd"; + final String itemName = "box-logo.png"; + final Date dueAt = BoxDateFormat.parse("2014-04-03T11:09:43-07:00"); + final BoxTask.Action action = BoxTask.Action.REVIEW; + final String message = "REVIEW PLZ K THX"; + final int assignmentCount = 0; + final boolean isCompleted = false; + final String createdByID = "11993747"; + final String createdByName = "sean"; + final String createdByLogin = "sean@box.com"; + final Date createdAt = BoxDateFormat.parse("2013-04-03T11:12:54-07:00"); + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"task\",\n" + + " \"id\": \"1839355\",\n" + + " \"item\": {\n" + + " \"type\": \"file\",\n" + + " \"id\": \"7287087200\",\n" + + " \"sequence_id\": \"0\",\n" + + " \"etag\": \"0\",\n" + + " \"sha1\": \"0bbd79a105c504f99573e3799756debba4c760cd\",\n" + + " \"name\": \"box-logo.png\"\n" + + " },\n" + + " \"due_at\": \"2014-04-03T11:09:43-07:00\",\n" + + " \"action\": \"review\",\n" + + " \"message\": \"REVIEW PLZ K THX\",\n" + + " \"task_assignment_collection\": {\n" + + " \"total_count\": 0,\n" + + " \"entries\": []\n" + + " },\n" + + " \"is_completed\": false,\n" + + " \"created_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"11993747\",\n" + + " \"name\": \"sean\",\n" + + " \"login\": \"sean@box.com\"\n" + + " },\n" + + " \"created_at\": \"2013-04-03T11:12:54-07:00\"\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxTask.Info info = new BoxTask(api, id).getInfo(); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(itemID, info.getItem().getID()); + Assert.assertEquals(itemSequenceID, info.getItem().getSequenceID()); + Assert.assertEquals(itemEtag, info.getItem().getEtag()); + Assert.assertEquals(itemSha1, info.getItem().getSha1()); + Assert.assertEquals(itemName, info.getItem().getName()); + Assert.assertEquals(dueAt, info.getDueAt()); + Assert.assertEquals(action, info.getAction()); + Assert.assertEquals(message, info.getMessage()); + Assert.assertEquals(assignmentCount, info.getTaskAssignments().size()); + Assert.assertEquals(isCompleted, info.isCompleted()); + Assert.assertEquals(createdByID, info.getCreatedBy().getID()); + Assert.assertEquals(createdByName, info.getCreatedBy().getName()); + Assert.assertEquals(createdByLogin, info.getCreatedBy().getLogin()); + Assert.assertEquals(createdAt, info.getCreatedAt()); + } + + /** + * Unit test for {@link BoxTask#updateInfo(BoxTask.Info)} + */ + @Test + @Category(UnitTest.class) + public void testUpdateInfoSendsCorrectJson() throws ParseException { + final String action = "review"; + final String message = "text message"; + final Date dueAt = BoxDateFormat.parse("2016-05-09T17:41:27-07:00"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals("https://api.box.com/2.0/tasks/0", + request.getUrl().toString()); + Assert.assertEquals(action, json.get("action").asString()); + Assert.assertEquals(message, json.get("message").asString()); + try { + Assert.assertEquals(dueAt, BoxDateFormat.parse(json.get("due_at").asString())); + } catch (ParseException e) { + assert false; + } + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + BoxTask task = new BoxTask(api, "0"); + BoxTask.Info info = task.new Info(); + info.addPendingChange("message", message); + info.addPendingChange("action", action); + info.addPendingChange("due_at", BoxDateFormat.format(dueAt)); + task.updateInfo(info); + } + + /** + * Unit test for {@link BoxTask#updateInfo(BoxTask.Info)} + */ + @Test + @Category(UnitTest.class) + public void testUpdateInfoParseAllFieldsCorrectly() throws ParseException { + final String id = "1839355"; + final String itemID = "7287087200"; + final String itemSequenceID = "0"; + final String itemEtag = "0"; + final String itemSha1 = "0bbd79a105c504f99573e3799756debba4c760cd"; + final String itemName = "box-logo.png"; + final Date dueAt = BoxDateFormat.parse("2014-04-03T11:09:43-07:00"); + final BoxTask.Action action = BoxTask.Action.REVIEW; + final String message = "REVIEW PLZ K THX"; + final int assignmentCount = 0; + final boolean isCompleted = false; + final String createdByID = "11993747"; + final String createdByName = "sean"; + final String createdByLogin = "sean@box.com"; + final Date createdAt = BoxDateFormat.parse("2013-04-03T11:12:54-07:00"); + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"task\",\n" + + " \"id\": \"1839355\",\n" + + " \"item\": {\n" + + " \"type\": \"file\",\n" + + " \"id\": \"7287087200\",\n" + + " \"sequence_id\": \"0\",\n" + + " \"etag\": \"0\",\n" + + " \"sha1\": \"0bbd79a105c504f99573e3799756debba4c760cd\",\n" + + " \"name\": \"box-logo.png\"\n" + + " },\n" + + " \"due_at\": \"2014-04-03T11:09:43-07:00\",\n" + + " \"action\": \"review\",\n" + + " \"message\": \"REVIEW PLZ K THX\",\n" + + " \"task_assignment_collection\": {\n" + + " \"total_count\": 0,\n" + + " \"entries\": []\n" + + " },\n" + + " \"is_completed\": false,\n" + + " \"created_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"11993747\",\n" + + " \"name\": \"sean\",\n" + + " \"login\": \"sean@box.com\"\n" + + " },\n" + + " \"created_at\": \"2013-04-03T11:12:54-07:00\"\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxTask task = new BoxTask(api, id); + BoxTask.Info info = task.new Info(); + info.addPendingChange("message", message); + task.updateInfo(info); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(itemID, info.getItem().getID()); + Assert.assertEquals(itemSequenceID, info.getItem().getSequenceID()); + Assert.assertEquals(itemEtag, info.getItem().getEtag()); + Assert.assertEquals(itemSha1, info.getItem().getSha1()); + Assert.assertEquals(itemName, info.getItem().getName()); + Assert.assertEquals(dueAt, info.getDueAt()); + Assert.assertEquals(action, info.getAction()); + Assert.assertEquals(message, info.getMessage()); + Assert.assertEquals(assignmentCount, info.getTaskAssignments().size()); + Assert.assertEquals(isCompleted, info.isCompleted()); + Assert.assertEquals(createdByID, info.getCreatedBy().getID()); + Assert.assertEquals(createdByName, info.getCreatedBy().getName()); + Assert.assertEquals(createdByLogin, info.getCreatedBy().getLogin()); + Assert.assertEquals(createdAt, info.getCreatedAt()); + } + + /** + * Unit test for {@link BoxTask#delete()} + */ + @Test + @Category(UnitTest.class) + public void testDeleteSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/tasks/0", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + new BoxTask(api, "0").delete(); + } + + /** + * Unit test for {@link BoxTask#addAssignmentByLogin(String)} + */ + @Test + @Category(UnitTest.class) + public void testAddAssignmentByLoginSendsCorrectJson() { + final String taskType = "task"; + final String taskID = "0"; + final String assignToLogin = "login@somewhere.com"; + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals("https://api.box.com/2.0/task_assignments", + request.getUrl().toString()); + Assert.assertEquals(taskType, json.get("task").asObject().get("type").asString()); + Assert.assertEquals(taskID, json.get("task").asObject().get("id").asString()); + Assert.assertEquals(assignToLogin, json.get("assign_to").asObject().get("login").asString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + new BoxTask(api, taskID).addAssignmentByLogin(assignToLogin); + } + + /** + * Unit test for {@link BoxTask#addAssignment(BoxUser)} + */ + @Test + @Category(UnitTest.class) + public void testAddAssignmentSendsCorrectJson() { + final String taskType = "task"; + final String taskID = "0"; + final String assignToID = "1"; + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals("https://api.box.com/2.0/task_assignments", + request.getUrl().toString()); + Assert.assertEquals(taskType, json.get("task").asObject().get("type").asString()); + Assert.assertEquals(taskID, json.get("task").asObject().get("id").asString()); + Assert.assertEquals(assignToID, json.get("assign_to").asObject().get("id").asString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + BoxUser user = new BoxUser(api, assignToID); + new BoxTask(api, taskID).addAssignment(user); + } + + /** + * Unit test for {@link BoxTask#addAssignment(BoxUser)} + */ + @Test + @Category(UnitTest.class) + public void testAddAssignmentParseAllFieldsCorrectly() throws ParseException { + final String id = "2698512"; + final String itemID = "8018809384"; + final String itemSequenceID = "0"; + final String itemEtag = "0"; + final String itemSha1 = "7840095ee096ee8297676a138d4e316eabb3ec96"; + final String itemName = "scrumworksToTrello.js"; + final String assignedToID = "1992432"; + final String assignedToName = "rhaegar@box.com"; + final String assignedToLogin = "rhaegar@box.com"; + final String message = null; + final Date completedAt = null; + final Date assignedAt = BoxDateFormat.parse("2013-05-10T11:43:41-07:00"); + final Date remindedAt = null; + final BoxTaskAssignment.ResolutionState resolutionState = BoxTaskAssignment.ResolutionState.INCOMPLETE; + final String assignedByID = "11993747"; + final String assignedByName = "sean"; + final String assignedByLogin = "sean@box.com"; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"task_assignment\",\n" + + " \"id\": \"2698512\",\n" + + " \"item\": {\n" + + " \"type\": \"file\",\n" + + " \"id\": \"8018809384\",\n" + + " \"sequence_id\": \"0\",\n" + + " \"etag\": \"0\",\n" + + " \"sha1\": \"7840095ee096ee8297676a138d4e316eabb3ec96\",\n" + + " \"name\": \"scrumworksToTrello.js\"\n" + + " },\n" + + " \"assigned_to\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"1992432\",\n" + + " \"name\": \"rhaegar@box.com\",\n" + + " \"login\": \"rhaegar@box.com\"\n" + + " },\n" + + " \"message\": null,\n" + + " \"completed_at\": null,\n" + + " \"assigned_at\": \"2013-05-10T11:43:41-07:00\",\n" + + " \"reminded_at\": null,\n" + + " \"resolution_state\": \"incomplete\",\n" + + " \"assigned_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"11993747\",\n" + + " \"name\": \"sean\",\n" + + " \"login\": \"sean@box.com\"\n" + + " }\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxUser user = new BoxUser(api, assignedToID); + BoxTaskAssignment.Info info = new BoxTask(api, id).addAssignment(user); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(itemID, info.getItem().getID()); + Assert.assertEquals(itemSequenceID, info.getItem().getSequenceID()); + Assert.assertEquals(itemEtag, info.getItem().getEtag()); + Assert.assertEquals(itemSha1, ((BoxFile.Info) info.getItem()).getSha1()); + Assert.assertEquals(itemName, info.getItem().getName()); + Assert.assertEquals(assignedToID, info.getAssignedTo().getID()); + Assert.assertEquals(assignedToName, info.getAssignedTo().getName()); + Assert.assertEquals(assignedToLogin, info.getAssignedTo().getLogin()); + Assert.assertEquals(message, info.getMessage()); + Assert.assertEquals(completedAt, info.getCompletedAt()); + Assert.assertEquals(assignedAt, info.getAssignedAt()); + Assert.assertEquals(remindedAt, info.getRemindedAt()); + Assert.assertEquals(resolutionState, info.getResolutionState()); + Assert.assertEquals(assignedByID, info.getAssignedBy().getID()); + Assert.assertEquals(assignedByName, info.getAssignedBy().getName()); + Assert.assertEquals(assignedByLogin, info.getAssignedBy().getLogin()); + } + + /** + * Unit test for {@link BoxTask#getAllAssignments(String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetAllAssignmentsSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals( + "https://api.box.com/2.0/tasks/0/assignments?fields=item%2Cmessage&limit=100&offset=0", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"total_count\": 1, \"offset\": 0, \"entries\":[]}"; + } + }; + } + }); + + Iterator iterator + = new BoxTask(api, "0").getAllAssignments("item", "message").iterator(); + iterator.hasNext(); + } + + /** + * Unit test for {@link BoxTask#getAllAssignments(String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetAllAssignmentsParseAllFieldsCorrectly() { + final String firstId = "2485961"; + final String firstItemID = "7287087200"; + final String firstItemSequenceID = "0"; + final String firstItemEtag = "0"; + final String firstItemSha1 = "0bbd79a105c504f99573e3799756debba4c760cd"; + final String firstItemName = "box-logo.png"; + final String firstAssignedToID = "193425559"; + final String firstAssignedToName = "Rhaegar Targaryen"; + final String firstAssignedToLogin = "rhaegar@box.com"; + final String secondId = "2485963"; + final String secondItemID = "7287087204"; + final String secondItemSequenceID = "1"; + final String secondItemEtag = "1"; + final String secondItemSha1 = "0bbd79a105c504f99573e3799756debb"; + final String secondItemName = "box-icon.png"; + final String secondAssignedToID = "1934255"; + final String secondAssignedToName = "Tyrion Lannister"; + final String secondAssignedToLogin = "littlebig@box.com"; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"total_count\": 2,\n" + + " \"offset\": 0,\n" + + " \"entries\": [\n" + + " {\n" + + " \"type\": \"task_assignment\",\n" + + " \"id\": \"2485961\",\n" + + " \"item\": {\n" + + " \"type\": \"file\",\n" + + " \"id\": \"7287087200\",\n" + + " \"sequence_id\": \"0\",\n" + + " \"etag\": \"0\",\n" + + " \"sha1\": \"0bbd79a105c504f99573e3799756debba4c760cd\",\n" + + " \"name\": \"box-logo.png\"\n" + + " },\n" + + " \"assigned_to\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"193425559\",\n" + + " \"name\": \"Rhaegar Targaryen\",\n" + + " \"login\": \"rhaegar@box.com\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"type\": \"task_assignment\",\n" + + " \"id\": \"2485963\",\n" + + " \"item\": {\n" + + " \"type\": \"file\",\n" + + " \"id\": \"7287087204\",\n" + + " \"sequence_id\": \"1\",\n" + + " \"etag\": \"1\",\n" + + " \"sha1\": \"0bbd79a105c504f99573e3799756debb\",\n" + + " \"name\": \"box-icon.png\"\n" + + " },\n" + + " \"assigned_to\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"1934255\",\n" + + " \"name\": \"Tyrion Lannister\",\n" + + " \"login\": \"littlebig@box.com\"\n" + + " }\n" + + " }\n" + + " ]\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + Iterator iterator = new BoxTask(api, "0").getAllAssignments().iterator(); + BoxTaskAssignment.Info info = iterator.next(); + Assert.assertEquals(firstId, info.getID()); + Assert.assertEquals(firstItemID, info.getItem().getID()); + Assert.assertEquals(firstItemSequenceID, info.getItem().getSequenceID()); + Assert.assertEquals(firstItemEtag, info.getItem().getEtag()); + Assert.assertEquals(firstItemSha1, ((BoxFile.Info) info.getItem()).getSha1()); + Assert.assertEquals(firstItemName, info.getItem().getName()); + Assert.assertEquals(firstAssignedToID, info.getAssignedTo().getID()); + Assert.assertEquals(firstAssignedToName, info.getAssignedTo().getName()); + Assert.assertEquals(firstAssignedToLogin, info.getAssignedTo().getLogin()); + info = iterator.next(); + Assert.assertEquals(secondId, info.getID()); + Assert.assertEquals(secondItemID, info.getItem().getID()); + Assert.assertEquals(secondItemSequenceID, info.getItem().getSequenceID()); + Assert.assertEquals(secondItemEtag, info.getItem().getEtag()); + Assert.assertEquals(secondItemSha1, ((BoxFile.Info) info.getItem()).getSha1()); + Assert.assertEquals(secondItemName, info.getItem().getName()); + Assert.assertEquals(secondAssignedToID, info.getAssignedTo().getID()); + Assert.assertEquals(secondAssignedToName, info.getAssignedTo().getName()); + Assert.assertEquals(secondAssignedToLogin, info.getAssignedTo().getLogin()); + Assert.assertEquals(false, iterator.hasNext()); + } + @Test @Category(IntegrationTest.class) public void updateInfoSucceeds() { From 7275777cfa3bb9ffb105db85f9c8fdcec736824c Mon Sep 17 00:00:00 2001 From: David Maynard Date: Wed, 16 Nov 2016 19:12:37 -0800 Subject: [PATCH 013/119] Adding isWatermarked to BoxItem. --- src/main/java/com/box/sdk/BoxItem.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/box/sdk/BoxItem.java b/src/main/java/com/box/sdk/BoxItem.java index 7248b1a32..c0b7c57b3 100644 --- a/src/main/java/com/box/sdk/BoxItem.java +++ b/src/main/java/com/box/sdk/BoxItem.java @@ -24,7 +24,7 @@ public abstract class BoxItem extends BoxResource { "content_modified_at", "created_by", "modified_by", "owned_by", "shared_link", "parent", "item_status", "version_number", "comment_count", "permissions", "tags", "lock", "extension", "is_package", "folder_upload_email", "item_collection", "sync_state", "has_collaborations", "can_non_owners_invite", - "file_version", "collections"}; + "file_version", "collections", "watermark_info"}; private static final URLTemplate SHARED_ITEM_URL_TEMPLATE = new URLTemplate("shared_items"); @@ -153,6 +153,9 @@ public abstract class Info extends BoxResource.Info { private BoxFolder.Info parent; private String itemStatus; private Set collections; + private Boolean isWatermarked; + + /** * Constructs an empty Info object. @@ -373,6 +376,15 @@ public Iterable getCollections() { return this.collections; } + + /** + * Gets flag indicating whether this file is Watermarked. + * @return whether the file is watermarked or not + */ + public Boolean getIsWatermarked() { + return this.isWatermarked; + } + /** * Sets the collections that this item belongs to. * @param collections the new list of collections that this item should belong to. @@ -466,6 +478,9 @@ protected void parseJSONMember(JsonObject.Member member) { BoxCollection.Info collectionInfo = collection.new Info(jsonObject); this.collections.add(collectionInfo); } + } else if (memberName.equals("watermark_info")) { + JsonObject jsonObject = value.asObject(); + this.isWatermarked = jsonObject.get("is_watermarked").asBoolean(); } } catch (ParseException e) { assert false : "A ParseException indicates a bug in the SDK."; From b3033dbe65d12792b5a03ad783a630cd42b86827 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Thu, 17 Nov 2016 15:59:08 -0800 Subject: [PATCH 014/119] Moving is_watermarked from BoxItem to BoxFile and BoxFolder --- src/main/java/com/box/sdk/BoxFile.java | 12 ++++++++++++ src/main/java/com/box/sdk/BoxFolder.java | 12 ++++++++++++ src/main/java/com/box/sdk/BoxItem.java | 13 +------------ 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 8530ac0ae..d98d2c69e 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -791,6 +791,7 @@ public class Info extends BoxItem.Info { private BoxFileVersion version; private URL previewLink; private BoxLock lock; + private boolean isWatermarked; /** * Constructs an empty Info object. @@ -892,6 +893,14 @@ public URL getPreviewLink() { return this.previewLink; } + /** + * Gets flag indicating whether this file is Watermarked. + * @return whether the file is watermarked or not + */ + public Boolean getIsWatermarked() { + return this.isWatermarked; + } + @Override protected void parseJSONMember(JsonObject.Member member) { super.parseJSONMember(member); @@ -925,6 +934,9 @@ protected void parseJSONMember(JsonObject.Member member) { } else { this.lock = new BoxLock(value.asObject(), BoxFile.this.getAPI()); } + } else if (memberName.equals("watermark_info")) { + JsonObject jsonObject = value.asObject(); + this.isWatermarked = jsonObject.get("is_watermarked").asBoolean(); } } diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index 8d8c70010..5c2bd5a28 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -692,6 +692,7 @@ public class Info extends BoxItem.Info { private SyncState syncState; private EnumSet permissions; private boolean canNonOwnersInvite; + private boolean isWatermarked; /** * Constructs an empty Info object. @@ -784,6 +785,14 @@ public boolean getCanNonOwnersInvite() { return this.canNonOwnersInvite; } + /** + * Gets flag indicating whether this file is Watermarked. + * @return whether the file is watermarked or not + */ + public Boolean getIsWatermarked() { + return this.isWatermarked; + } + @Override public BoxFolder getResource() { return BoxFolder.this; @@ -813,6 +822,9 @@ protected void parseJSONMember(JsonObject.Member member) { } else if (memberName.equals("can_non_owners_invite")) { this.canNonOwnersInvite = value.asBoolean(); + } else if (memberName.equals("watermark_info")) { + JsonObject jsonObject = value.asObject(); + this.isWatermarked = jsonObject.get("is_watermarked").asBoolean(); } } diff --git a/src/main/java/com/box/sdk/BoxItem.java b/src/main/java/com/box/sdk/BoxItem.java index c0b7c57b3..50ea912ac 100644 --- a/src/main/java/com/box/sdk/BoxItem.java +++ b/src/main/java/com/box/sdk/BoxItem.java @@ -153,7 +153,7 @@ public abstract class Info extends BoxResource.Info { private BoxFolder.Info parent; private String itemStatus; private Set collections; - private Boolean isWatermarked; + @@ -377,14 +377,6 @@ public Iterable getCollections() { } - /** - * Gets flag indicating whether this file is Watermarked. - * @return whether the file is watermarked or not - */ - public Boolean getIsWatermarked() { - return this.isWatermarked; - } - /** * Sets the collections that this item belongs to. * @param collections the new list of collections that this item should belong to. @@ -478,9 +470,6 @@ protected void parseJSONMember(JsonObject.Member member) { BoxCollection.Info collectionInfo = collection.new Info(jsonObject); this.collections.add(collectionInfo); } - } else if (memberName.equals("watermark_info")) { - JsonObject jsonObject = value.asObject(); - this.isWatermarked = jsonObject.get("is_watermarked").asBoolean(); } } catch (ParseException e) { assert false : "A ParseException indicates a bug in the SDK."; From 009a033789d8fed11efb4de51220244e93dc6e90 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Thu, 17 Nov 2016 16:11:06 -0800 Subject: [PATCH 015/119] Moving "watermark_info" fromBoxIyem ALL_FIELDS to BoxFile and BoxFolder --- src/main/java/com/box/sdk/BoxFile.java | 2 +- src/main/java/com/box/sdk/BoxFolder.java | 2 +- src/main/java/com/box/sdk/BoxItem.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index d98d2c69e..acab1c1ca 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -35,7 +35,7 @@ public class BoxFile extends BoxItem { "description", "size", "path_collection", "created_at", "modified_at", "trashed_at", "purged_at", "content_created_at", "content_modified_at", "created_by", "modified_by", "owned_by", "shared_link", "parent", "item_status", "version_number", "comment_count", "permissions", "tags", "lock", "extension", "is_package", - "file_version", "collections"}; + "file_version", "collections", "watermark_info"}; /** * Used to specify what filetype to request for a file thumbnail. diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index 5c2bd5a28..91f9fe88a 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -29,7 +29,7 @@ public class BoxFolder extends BoxItem implements Iterable { "description", "size", "path_collection", "created_by", "modified_by", "trashed_at", "purged_at", "content_created_at", "content_modified_at", "owned_by", "shared_link", "folder_upload_email", "parent", "item_status", "item_collection", "sync_state", "has_collaborations", "permissions", "tags", - "can_non_owners_invite", "collections"}; + "can_non_owners_invite", "collections", "watermark_info"}; private static final URLTemplate CREATE_FOLDER_URL = new URLTemplate("folders"); private static final URLTemplate CREATE_WEB_LINK_URL = new URLTemplate("web_links"); diff --git a/src/main/java/com/box/sdk/BoxItem.java b/src/main/java/com/box/sdk/BoxItem.java index 50ea912ac..9de43eca6 100644 --- a/src/main/java/com/box/sdk/BoxItem.java +++ b/src/main/java/com/box/sdk/BoxItem.java @@ -24,7 +24,7 @@ public abstract class BoxItem extends BoxResource { "content_modified_at", "created_by", "modified_by", "owned_by", "shared_link", "parent", "item_status", "version_number", "comment_count", "permissions", "tags", "lock", "extension", "is_package", "folder_upload_email", "item_collection", "sync_state", "has_collaborations", "can_non_owners_invite", - "file_version", "collections", "watermark_info"}; + "file_version", "collections"}; private static final URLTemplate SHARED_ITEM_URL_TEMPLATE = new URLTemplate("shared_items"); From ae57191383a2862755dbba4d15981181ee282e82 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Thu, 17 Nov 2016 16:16:56 -0800 Subject: [PATCH 016/119] removing extraneous blank lines from BoxItem.java --- src/main/java/com/box/sdk/BoxItem.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxItem.java b/src/main/java/com/box/sdk/BoxItem.java index 9de43eca6..e32bfa5d0 100644 --- a/src/main/java/com/box/sdk/BoxItem.java +++ b/src/main/java/com/box/sdk/BoxItem.java @@ -154,9 +154,6 @@ public abstract class Info extends BoxResource.Info { private String itemStatus; private Set collections; - - - /** * Constructs an empty Info object. */ @@ -375,8 +372,7 @@ public String getItemStatus() { public Iterable getCollections() { return this.collections; } - - + /** * Sets the collections that this item belongs to. * @param collections the new list of collections that this item should belong to. From f063d3d5f839604f279f24984796162efd573e79 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Thu, 17 Nov 2016 16:18:51 -0800 Subject: [PATCH 017/119] Removing trailing whitespace from BoxItem.java #375 --- src/main/java/com/box/sdk/BoxItem.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/box/sdk/BoxItem.java b/src/main/java/com/box/sdk/BoxItem.java index e32bfa5d0..7248b1a32 100644 --- a/src/main/java/com/box/sdk/BoxItem.java +++ b/src/main/java/com/box/sdk/BoxItem.java @@ -372,7 +372,7 @@ public String getItemStatus() { public Iterable getCollections() { return this.collections; } - + /** * Sets the collections that this item belongs to. * @param collections the new list of collections that this item should belong to. From 4a4223d45fb5b5046c971b512b6f168d31096015 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Thu, 17 Nov 2016 21:18:54 -0800 Subject: [PATCH 018/119] Changed type of getIsWatermarked from Boolean to boolean --- src/main/java/com/box/sdk/BoxFile.java | 2 +- src/main/java/com/box/sdk/BoxFolder.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index acab1c1ca..57bc6edaf 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -897,7 +897,7 @@ public URL getPreviewLink() { * Gets flag indicating whether this file is Watermarked. * @return whether the file is watermarked or not */ - public Boolean getIsWatermarked() { + public boolean getIsWatermarked() { return this.isWatermarked; } diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index 91f9fe88a..e0d6d4acd 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -789,7 +789,7 @@ public boolean getCanNonOwnersInvite() { * Gets flag indicating whether this file is Watermarked. * @return whether the file is watermarked or not */ - public Boolean getIsWatermarked() { + public boolean getIsWatermarked() { return this.isWatermarked; } From 9720ab151e4481263e59680ec95d8a0702cc2899 Mon Sep 17 00:00:00 2001 From: gleb-urvanov Date: Tue, 25 Oct 2016 12:10:45 +0200 Subject: [PATCH 019/119] unit tests for BoxWeblink methods --- README.md | 1 + doc/weblinks.md | 68 ++++ src/test/java/com/box/sdk/BoxFolderTest.java | 247 +++++++++++++- src/test/java/com/box/sdk/BoxWebLinkTest.java | 318 +++++++++++++++++- 4 files changed, 630 insertions(+), 4 deletions(-) create mode 100644 doc/weblinks.md diff --git a/README.md b/README.md index 4344725bd..d4275e6ed 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,7 @@ You can find guides and tutorials in the `doc` directory. * [Devices](doc/devices.md) * [Retention Policies](doc/retention_policies.md) * [Legal Holds Policy](doc/legal_holds.md) +* [Web Links](doc/weblinks.md) Javadocs are generated when `gradle javadoc` is run and can be found in `build/doc/javadoc`. diff --git a/doc/weblinks.md b/doc/weblinks.md new file mode 100644 index 000000000..3a606c935 --- /dev/null +++ b/doc/weblinks.md @@ -0,0 +1,68 @@ +Web Links +====== + +Web links are objects that point to URLs. These objects are also known as bookmarks within the Box web application. Web link objects are treated similarly to file objects. + +* [Create Web Link](#create-web-link) +* [Get Web Link](#get-web-link) +* [Update Web Link](#update-web-link) +* [Delete Web Link](#delete-web-link) + +Create Web Link +-------------- + +Calling [`BoxFolder.createWebLink(String, URL, String)`][create-web-link] will let you create a new web link with a specified name and description. + +```java +BoxFolder folder = new BoxFolder(api, id); +folder.createWebLink("name", url, "description"); +``` + +Name and description params are optional, so it is possible to create web link with only one of them or with URL only: [`BoxFolder.createWebLink(URL)`][create-web-link2] + +```java +BoxFolder folder = new BoxFolder(api, id); +BoxWebLink.Info webLinkInfo = folder.createWebLink(url); +``` + +[create-web-link]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFolder.html#createWebLink(java.lang.String,%20java.net.URL,%20java.lang.String) +[create-web-link2]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFolder.html#createWebLink(java.net.URL) + +Get Web Link +-------------- + +A web link info can be retrieved by calling the [`getInfo(String...)`][get-web-link] method. +Optional parameters can be used to retrieve specific fields of the Device Pin object. + +```java +BoxWebLink webLink = new BoxWebLink(api, id); +BoxWebLink.Info webLinkInfo = webLink.getInfo(); +``` + +[get-web-link]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxWebLink.html#getInfo(java.lang.String...) + +Update Web Link +-------------- + +A web link can be updated by calling the [`updateInfo(BoxWebLink.Info)`][update-web-link] method. + +```java +BoxWebLink webLink = new BoxWebLink(api, id); +BoxWebLink.Info webLinkInfo = webLink.new Info(); +webLinkInfo.addPendingChange("name", "new name for weblink"); +webLink.updateInfo(webLinkInfo); +``` + +[update-web-link]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxWebLink.html#updateInfo(com.box.sdk.BoxWebLink.Info) + +Delete Web Link +--------------------------- + +A web link can be deleted by calling the [`delete()`][delete] method. + +```java +BoxWebLink webLink = new BoxWebLink(api, id); +webLink.delete(); +``` + +[delete]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxWebLink.html#delete() \ No newline at end of file diff --git a/src/test/java/com/box/sdk/BoxFolderTest.java b/src/test/java/com/box/sdk/BoxFolderTest.java index 1085216ab..078f33a69 100644 --- a/src/test/java/com/box/sdk/BoxFolderTest.java +++ b/src/test/java/com/box/sdk/BoxFolderTest.java @@ -4,8 +4,14 @@ import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; +import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.*; +import java.util.Collection; +import java.util.Date; +import java.util.EnumSet; +import java.util.List; +import java.util.Scanner; +import java.util.TimeZone; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; @@ -19,24 +25,42 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.skyscreamer.jsonassert.JSONCompareMode.*; +import static org.skyscreamer.jsonassert.JSONCompareMode.LENIENT; import org.hamcrest.Matchers; +import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; -import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.containing; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import com.eclipsesource.json.JsonArray; import com.eclipsesource.json.JsonObject; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.junit.WireMockRule; +/** + * {@link BoxFolder} related tests. + */ public class BoxFolderTest { + + /** + * Wiremock + */ @Rule public final WireMockRule wireMockRule = new WireMockRule(8080); + /** + * Unit test of equality of two {@link BoxFolder} objects with the same ID. + */ @Test @Category(UnitTest.class) public void foldersWithSameIDAreEqual() { @@ -47,6 +71,9 @@ public void foldersWithSameIDAreEqual() { assertThat(folder1, equalTo(folder2)); } + /** + * Unit test for {@link BoxFolder#createFolder(String)}. + */ @Test @Category(UnitTest.class) public void createFolderSendsRequestWithRequiredFields() { @@ -66,6 +93,9 @@ public void createFolderSendsRequestWithRequiredFields() { rootFolder.createFolder(createdFolderName); } + /** + * Unit test for {@link BoxFolder.Info#Info(String)}. + */ @Test @Category(UnitTest.class) public void infoParsesMixedPermissionsCorrectly() { @@ -95,6 +125,9 @@ public void infoParsesMixedPermissionsCorrectly() { assertThat(info.getPermissions(), is(equalTo(expectedPermissions))); } + /** + * Unit test for {@link BoxFolder#getChildrenRange(long, long, String...)}. + */ @Test @Category(UnitTest.class) public void getChildrenRangeRequestsCorrectOffsetLimitAndFields() { @@ -118,6 +151,9 @@ public void getChildrenRangeRequestsCorrectOffsetLimitAndFields() { assertThat(children.fullSize(), is(equalTo(3L))); } + /** + * Unit test for {@link BoxFolder#collaborate(BoxCollaborator, BoxCollaboration.Role)}. + */ @Test @Category(UnitTest.class) public void collaborateShouldSendCorrectJSONWhenCollaboratingWithAGroup() { @@ -157,6 +193,9 @@ public String getJSON() { folder.collaborate(collaborator, BoxCollaboration.Role.CO_OWNER); } + /** + * Unit test for {@link BoxFolder#getCollaborations()}. + */ @Test @Category(UnitTest.class) public void getCollaborationsShouldParseGroupsCorrectly() { @@ -198,6 +237,208 @@ public String getJSON() { } } + /** + * Unit test for {@link BoxFolder#createWebLink(String, URL, String)}. + */ + @Test + @Category(UnitTest.class) + public void testCreateWeblinkSendsCorrectJsonWithNameAndDescription() throws MalformedURLException { + final String url = "https://www.box.com/home"; + final String parentFolderID = "0"; + final String name = "non-empty name"; + final String description = "non-empty description"; + + final JsonObject fakeJSONResponse = new JsonObject() + .add("type", "web_link") + .add("id", "0"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/web_links", request.getUrl().toString()); + Scanner body = new Scanner(request.getBody()).useDelimiter("\n"); + JsonObject json = JsonObject.readFrom(body.next()); + body.close(); + Assert.assertEquals(url, json.get("url").asString()); + Assert.assertEquals(parentFolderID, json.get("parent").asObject().get("id").asString()); + Assert.assertEquals(name, json.get("name").asString()); + Assert.assertEquals(description, json.get("description").asString()); + + return new BoxJSONResponse() { + @Override + public String getJSON() { + return fakeJSONResponse.toString(); + } + }; + } + }); + + new BoxFolder(api, "0").createWebLink(name, new URL(url), description); + } + + /** + * Unit test for {@link BoxFolder#createWebLink(URL)}. + */ + @Test + @Category(UnitTest.class) + public void testCreateWeblinkSendsCorrectJsonWithoutNameAndDescription() throws MalformedURLException { + final String url = "https://www.box.com/home"; + final String parentFolderID = "0"; + + final JsonObject fakeJSONResponse = new JsonObject() + .add("type", "web_link") + .add("id", "0"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/web_links", request.getUrl().toString()); + JsonObject json = JsonObject.readFrom(new Scanner(request.getBody()).useDelimiter("\n").next()); + assertEquals(url, json.get("url").asString()); + assertEquals(parentFolderID, json.get("parent").asObject().get("id").asString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return fakeJSONResponse.toString(); + } + }; + } + }); + + new BoxFolder(api, "0").createWebLink(new URL(url)); + } + + /** + * Unit test for {@link BoxFolder#createWebLink(URL)}. + */ + @Test + @Category(UnitTest.class) + public void testCreateWeblinkParseAllFieldsCorrectly() throws ParseException, MalformedURLException { + final String id = "6742981"; + final String sequenceID = "0"; + final String etag = "0"; + final String name = "Box Website"; + final String url = "https://www.box.com"; + final String creatorID = "10523870"; + final String creatorName = "Ted Blosser"; + final String creatorLogin = "ted+demo@box.com"; + final Date createdAt = BoxDateFormat.parse("2015-05-07T14:31:16-07:00"); + final Date modifiedAt = BoxDateFormat.parse("2015-05-07T14:31:16-07:00"); + final String parentID = "848123342"; + final String parentSequenceID = "1"; + final String parentEtag = "1"; + final String parentName = "Documentation"; + final String description = "Cloud Content Management"; + final String itemStatus = "active"; + final Date trashedAt = null; + final Date purgedAt = null; + final BoxSharedLink sharedLink = null; + final String pathID = "848123342"; + final String pathSequenceID = "1"; + final String pathEtag = "1"; + final String pathName = "Documentation"; + final String modifiedID = "10523870"; + final String modifiedName = "Ted Blosser"; + final String modifiedLogin = "ted+demo@box.com"; + final String ownerID = "10523870"; + final String ownerName = "Ted Blosser"; + final String ownerLogin = "ted+demo@box.com"; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"web_link\",\n" + + " \"id\": \"6742981\",\n" + + " \"sequence_id\": \"0\",\n" + + " \"etag\": \"0\",\n" + + " \"name\": \"Box Website\",\n" + + " \"url\": \"https://www.box.com\",\n" + + " \"created_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"10523870\",\n" + + " \"name\": \"Ted Blosser\",\n" + + " \"login\": \"ted+demo@box.com\"\n" + + " },\n" + + " \"created_at\": \"2015-05-07T14:31:16-07:00\",\n" + + " \"modified_at\": \"2015-05-07T14:31:16-07:00\",\n" + + " \"parent\": {\n" + + " \"type\": \"folder\",\n" + + " \"id\": \"848123342\",\n" + + " \"sequence_id\": \"1\",\n" + + " \"etag\": \"1\",\n" + + " \"name\": \"Documentation\"\n" + + " },\n" + + " \"description\": \"Cloud Content Management\",\n" + + " \"item_status\": \"active\",\n" + + " \"trashed_at\": null,\n" + + " \"purged_at\": null,\n" + + " \"shared_link\": null,\n" + + " \"path_collection\": {\n" + + " \"total_count\": 1,\n" + + " \"entries\": [\n" + + " {\n" + + " \"type\": \"folder\",\n" + + " \"id\": \"848123342\",\n" + + " \"sequence_id\": \"1\",\n" + + " \"etag\": \"1\",\n" + + " \"name\": \"Documentation\"\n" + + " }\n" + + " ]\n" + + " },\n" + + " \"modified_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"10523870\",\n" + + " \"name\": \"Ted Blosser\",\n" + + " \"login\": \"ted+demo@box.com\"\n" + + " },\n" + + " \"owned_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"10523870\",\n" + + " \"name\": \"Ted Blosser\",\n" + + " \"login\": \"ted+demo@box.com\"\n" + + " }\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxWebLink.Info info = new BoxFolder(api, "0").createWebLink(new URL(url)); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(sequenceID, info.getSequenceID()); + Assert.assertEquals(etag, info.getEtag()); + Assert.assertEquals(name, info.getName()); + Assert.assertEquals(url, info.getLinkURL().toString()); + Assert.assertEquals(createdAt, info.getCreatedAt()); + Assert.assertEquals(modifiedAt, info.getModifiedAt()); + Assert.assertEquals(description, info.getDescription()); + Assert.assertEquals(itemStatus, info.getItemStatus()); + Assert.assertEquals(trashedAt, info.getTrashedAt()); + Assert.assertEquals(purgedAt, info.getPurgedAt()); + Assert.assertEquals(sharedLink, info.getSharedLink()); + BoxUser.Info creatorInfo = info.getCreatedBy(); + Assert.assertEquals(creatorID, creatorInfo.getID()); + Assert.assertEquals(creatorName, creatorInfo.getName()); + Assert.assertEquals(creatorLogin, creatorInfo.getLogin()); + BoxUser.Info modifiedInfo = info.getModifiedBy(); + Assert.assertEquals(modifiedID, modifiedInfo.getID()); + Assert.assertEquals(modifiedName, modifiedInfo.getName()); + Assert.assertEquals(modifiedLogin, modifiedInfo.getLogin()); + BoxUser.Info ownerInfo = info.getOwnedBy(); + Assert.assertEquals(ownerID, ownerInfo.getID()); + Assert.assertEquals(ownerName, ownerInfo.getName()); + Assert.assertEquals(ownerLogin, ownerInfo.getLogin()); + BoxFolder.Info parentInfo = info.getParent(); + Assert.assertEquals(parentID, parentInfo.getID()); + Assert.assertEquals(parentSequenceID, parentInfo.getSequenceID()); + Assert.assertEquals(parentEtag, parentInfo.getEtag()); + Assert.assertEquals(parentName, parentInfo.getName()); + BoxFolder.Info pathInfo = info.getPathCollection().get(0); + Assert.assertEquals(pathID, pathInfo.getID()); + Assert.assertEquals(pathSequenceID, pathInfo.getSequenceID()); + Assert.assertEquals(pathEtag, pathInfo.getEtag()); + Assert.assertEquals(pathName, pathInfo.getName()); + } + @Test @Category(IntegrationTest.class) public void creatingAndDeletingFolderSucceeds() { diff --git a/src/test/java/com/box/sdk/BoxWebLinkTest.java b/src/test/java/com/box/sdk/BoxWebLinkTest.java index 63e9c904e..da2d328d2 100644 --- a/src/test/java/com/box/sdk/BoxWebLinkTest.java +++ b/src/test/java/com/box/sdk/BoxWebLinkTest.java @@ -2,16 +2,332 @@ import java.net.MalformedURLException; import java.net.URL; +import java.text.ParseException; +import java.util.Date; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; import org.hamcrest.Matchers; +import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; +import com.eclipsesource.json.JsonObject; + +/** + * {@link BoxWebLink} related tests. + */ public class BoxWebLinkTest { + /** + * Unit test for {@link BoxWebLink#copy(BoxFolder, String)}. + */ + @Test + @Category(UnitTest.class) + public void testcopyWebLinkSendsCorrectJson() { + final String parentFolderID = "0"; + final String name = "non-empty name"; + + final JsonObject fakeJSONResponse = new JsonObject() + .add("type", "web_link") + .add("id", "0"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals(parentFolderID, json.get("parent").asObject().get("id").asString()); + Assert.assertEquals(name, json.get("name").asString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return fakeJSONResponse.toString(); + } + }; + } + }); + + BoxWebLink weblink = new BoxWebLink(api, "0"); + weblink.copy(new BoxFolder(api, "0"), name); + } + + /** + * Unit test for {@link BoxWebLink#move(BoxFolder, String)}. + */ + @Test + @Category(UnitTest.class) + public void testMoveWebLinkSendsCorrectJson() { + final String parentFolderID = "0"; + final String name = "non-empty name"; + + final JsonObject fakeJSONResponse = new JsonObject() + .add("type", "web_link") + .add("id", "0"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals(parentFolderID, json.get("parent").asObject().get("id").asString()); + Assert.assertEquals(name, json.get("name").asString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return fakeJSONResponse.toString(); + } + }; + } + }); + + BoxWebLink weblink = new BoxWebLink(api, "0"); + weblink.move(new BoxFolder(api, "0"), name); + } + + /** + * Unit test for {@link BoxWebLink#rename(String)}. + */ + @Test + @Category(UnitTest.class) + public void testRenameWebLinkSendsCorrectJson() { + final String name = "non-empty name"; + + final JsonObject fakeJSONResponse = new JsonObject() + .add("type", "web_link") + .add("id", "0"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals(name, json.get("name").asString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return fakeJSONResponse.toString(); + } + }; + } + }); + + BoxWebLink weblink = new BoxWebLink(api, "0"); + weblink.rename(name); + } + + /** + * Unit test for {@link BoxWebLink#updateInfo(BoxWebLink.Info)}. + */ + @Test + @Category(UnitTest.class) + public void testUpdateInfoSendsCorrectJson() { + final String name = "non-empty name"; + + final JsonObject fakeJSONResponse = new JsonObject() + .add("type", "web_link") + .add("id", "0"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals("https://api.box.com/2.0/web_links/0", request.getUrl().toString()); + Assert.assertEquals(name, json.get("name").asString()); + + return new BoxJSONResponse() { + @Override + public String getJSON() { + return fakeJSONResponse.toString(); + } + }; + } + }); + + BoxWebLink weblink = new BoxWebLink(api, "0"); + BoxWebLink.Info info = weblink.new Info(); + info.addPendingChange("name", name); + weblink.updateInfo(info); + } + + /** + * Unit test for {@link BoxWebLink#delete()}. + */ + @Test + @Category(UnitTest.class) + public void testDeleteWeblinkSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/web_links/0", request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{}"; + } + }; + } + }); + + BoxWebLink weblink = new BoxWebLink(api, "0"); + weblink.delete(); + } + + /** + * Unit test for {@link BoxWebLink#getInfo()}. + */ + @Test + @Category(UnitTest.class) + public void testGetInfoSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/web_links/0", request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{}"; + } + }; + } + }); + + BoxWebLink weblink = new BoxWebLink(api, "0"); + weblink.getInfo(); + } + + /** + * Unit test for {@link BoxWebLink#getInfo()}. + */ + @Test + @Category(UnitTest.class) + public void testGetInfoParseAllFieldsCorrectly() throws ParseException { + final String id = "6742981"; + final String sequenceID = "0"; + final String etag = "0"; + final String name = "Box Website"; + final String url = "https://www.box.com"; + final String creatorID = "10523870"; + final String creatorName = "Ted Blosser"; + final String creatorLogin = "ted+demo@box.com"; + final Date createdAt = BoxDateFormat.parse("2015-05-07T14:31:16-07:00"); + final Date modifiedAt = BoxDateFormat.parse("2015-05-07T14:31:16-07:00"); + final String parentID = "848123342"; + final String parentSequenceID = "1"; + final String parentEtag = "1"; + final String parentName = "Documentation"; + final String description = "Cloud Content Management"; + final String itemStatus = "active"; + final Date trashedAt = null; + final Date purgedAt = null; + final BoxSharedLink sharedLink = null; + final String pathID = "848123342"; + final String pathSequenceID = "1"; + final String pathEtag = "1"; + final String pathName = "Documentation"; + final String modifiedID = "10523870"; + final String modifiedName = "Ted Blosser"; + final String modifiedLogin = "ted+demo@box.com"; + final String ownerID = "10523870"; + final String ownerName = "Ted Blosser"; + final String ownerLogin = "ted+demo@box.com"; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"web_link\",\n" + + " \"id\": \"6742981\",\n" + + " \"sequence_id\": \"0\",\n" + + " \"etag\": \"0\",\n" + + " \"name\": \"Box Website\",\n" + + " \"url\": \"https://www.box.com\",\n" + + " \"created_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"10523870\",\n" + + " \"name\": \"Ted Blosser\",\n" + + " \"login\": \"ted+demo@box.com\"\n" + + " },\n" + + " \"created_at\": \"2015-05-07T14:31:16-07:00\",\n" + + " \"modified_at\": \"2015-05-07T14:31:16-07:00\",\n" + + " \"parent\": {\n" + + " \"type\": \"folder\",\n" + + " \"id\": \"848123342\",\n" + + " \"sequence_id\": \"1\",\n" + + " \"etag\": \"1\",\n" + + " \"name\": \"Documentation\"\n" + + " },\n" + + " \"description\": \"Cloud Content Management\",\n" + + " \"item_status\": \"active\",\n" + + " \"trashed_at\": null,\n" + + " \"purged_at\": null,\n" + + " \"shared_link\": null,\n" + + " \"path_collection\": {\n" + + " \"total_count\": 1,\n" + + " \"entries\": [\n" + + " {\n" + + " \"type\": \"folder\",\n" + + " \"id\": \"848123342\",\n" + + " \"sequence_id\": \"1\",\n" + + " \"etag\": \"1\",\n" + + " \"name\": \"Documentation\"\n" + + " }\n" + + " ]\n" + + " },\n" + + " \"modified_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"10523870\",\n" + + " \"name\": \"Ted Blosser\",\n" + + " \"login\": \"ted+demo@box.com\"\n" + + " },\n" + + " \"owned_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"10523870\",\n" + + " \"name\": \"Ted Blosser\",\n" + + " \"login\": \"ted+demo@box.com\"\n" + + " }\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxWebLink.Info info = new BoxWebLink(api, "6742981").getInfo(); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(sequenceID, info.getSequenceID()); + Assert.assertEquals(etag, info.getEtag()); + Assert.assertEquals(name, info.getName()); + Assert.assertEquals(url, info.getLinkURL().toString()); + Assert.assertEquals(createdAt, info.getCreatedAt()); + Assert.assertEquals(modifiedAt, info.getModifiedAt()); + Assert.assertEquals(description, info.getDescription()); + Assert.assertEquals(itemStatus, info.getItemStatus()); + Assert.assertEquals(trashedAt, info.getTrashedAt()); + Assert.assertEquals(purgedAt, info.getPurgedAt()); + Assert.assertEquals(sharedLink, info.getSharedLink()); + BoxUser.Info creatorInfo = info.getCreatedBy(); + Assert.assertEquals(creatorID, creatorInfo.getID()); + Assert.assertEquals(creatorName, creatorInfo.getName()); + Assert.assertEquals(creatorLogin, creatorInfo.getLogin()); + BoxUser.Info modifiedInfo = info.getModifiedBy(); + Assert.assertEquals(modifiedID, modifiedInfo.getID()); + Assert.assertEquals(modifiedName, modifiedInfo.getName()); + Assert.assertEquals(modifiedLogin, modifiedInfo.getLogin()); + BoxUser.Info ownerInfo = info.getOwnedBy(); + Assert.assertEquals(ownerID, ownerInfo.getID()); + Assert.assertEquals(ownerName, ownerInfo.getName()); + Assert.assertEquals(ownerLogin, ownerInfo.getLogin()); + BoxFolder.Info parentInfo = info.getParent(); + Assert.assertEquals(parentID, parentInfo.getID()); + Assert.assertEquals(parentSequenceID, parentInfo.getSequenceID()); + Assert.assertEquals(parentEtag, parentInfo.getEtag()); + Assert.assertEquals(parentName, parentInfo.getName()); + BoxFolder.Info pathInfo = info.getPathCollection().get(0); + Assert.assertEquals(pathID, pathInfo.getID()); + Assert.assertEquals(pathSequenceID, pathInfo.getSequenceID()); + Assert.assertEquals(pathEtag, pathInfo.getEtag()); + Assert.assertEquals(pathName, pathInfo.getName()); + } + @Test @Category(IntegrationTest.class) public void copyWebLinkSucceeds() throws MalformedURLException { From e10b2e36f4c62a959778bc26afb236fdba4408ea Mon Sep 17 00:00:00 2001 From: gleb-urvanov Date: Fri, 11 Nov 2016 20:45:42 +0100 Subject: [PATCH 020/119] webhooks tested and documented --- README.md | 1 + doc/webhooks.md | 75 +++ src/main/java/com/box/sdk/BoxWebHook.java | 133 ++++++ src/test/java/com/box/sdk/BoxWebHookTest.java | 430 ++++++++++++++++++ 4 files changed, 639 insertions(+) create mode 100644 doc/webhooks.md diff --git a/README.md b/README.md index 4344725bd..96cf972f4 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,7 @@ You can find guides and tutorials in the `doc` directory. * [Devices](doc/devices.md) * [Retention Policies](doc/retention_policies.md) * [Legal Holds Policy](doc/legal_holds.md) +* [Webhooks](doc/webhooks.md) Javadocs are generated when `gradle javadoc` is run and can be found in `build/doc/javadoc`. diff --git a/doc/webhooks.md b/doc/webhooks.md new file mode 100644 index 000000000..604e1cd42 --- /dev/null +++ b/doc/webhooks.md @@ -0,0 +1,75 @@ +Webhooks +====== + +Webhooks enable you to attach event triggers to Box files and folders. Event triggers monitor events on Box objects and notify your application when they occur. A webhook notifies your application by sending HTTP requests to a URL of your choosing. + +* [Get a Webhook](#get-a-webhook) +* [Get All Webhooks](#get-all-webhooks) +* [Create a Webhook](#create-a-webhook) +* [Delete a Webhook](#delete-a-webhook) +* [Update a Webhook](#update-a-webhook) + +Get a Webhook +--------------------------- + +A webhook infocan be retrieved by calling the [`getInfo(String...)`][get-info] method. + +```java +BoxWebHook webhook = new BoxWebHook(api, id); +BoxWebHook.Info info = weghook.getInfo(); +``` + +[get-info]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxWebHook.html#getInfo(java.lang.String...) + +Get All Webhooks +-------------- + +Calling the static [`all(BoxAPIConnection, String...)`][all] will return an iterable that will page through all defined webhooks for the requesting application and user. + +```java +Iterable webhooks = BoxWebHook.all(BoxAPIConnection api); +for (BoxWebHook.Info webhookInfo: webhooks) { + // Do something with the webhook. +} +``` + +[all]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxWebHook.html#all(com.box.sdk.BoxAPIConnection,%20java.lang.String...) + +Create a Webhook +-------------- + +The static [`create(BoxResource, URL, BoxWebHook.Trigger...)`][create-webhook] method will +let you create a new webhook for a specified target object. + +```java +BoxFolder folder = new BoxFolder(api, id); +BoxWebHook.Info webhookInfo = BoxWebHook.create(folder, url, BoxWebHook.Trigger.FILE_UPLOADED); +``` + +[create-webhook]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxWebHook.html#create(com.box.sdk.BoxResource,%20java.net.URL,%20com.box.sdk.BoxWebHook.Trigger...) + +Delete a Webhook +-------------- + +A webhook can be deleted by calling the [`delete()`][delete] method. + +```java +BoxWebHook webhook = new BoxWebHook(api, id); +webhook.delete(); +``` + +[delete]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxWebHook.html#delete() + +Update a Webhook +-------------- + +A webhook can be updated by calling the [`update(BoxWebHook.Info)`][update] method. + +```java +BoxWebHook webhook = new BoxWebHook(api, id); +BoxWebHook.Info info = webhook.getInfo(); +info.addPendingChange("address", url); +webhook.update(info); +``` + +[update]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxWebHook.html#update(com.box.sdk.BoxWebHook.Info) \ No newline at end of file diff --git a/src/main/java/com/box/sdk/BoxWebHook.java b/src/main/java/com/box/sdk/BoxWebHook.java index 4cd696b09..266548bfe 100644 --- a/src/main/java/com/box/sdk/BoxWebHook.java +++ b/src/main/java/com/box/sdk/BoxWebHook.java @@ -2,9 +2,11 @@ import java.net.MalformedURLException; import java.net.URL; +import java.text.ParseException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Date; import java.util.HashSet; import java.util.Set; @@ -53,6 +55,16 @@ public class BoxWebHook extends BoxResource { */ private static final String JSON_KEY_TRIGGERS = "triggers"; + /** + * JSON Key for {@link BoxWebHook.Info#getCreatedBy()}. + */ + private static final String JSON_KEY_CREATED_BY = "created_by"; + + /** + * JSON Key for {@link BoxWebHook.Info#getCreatedAt()}. + */ + private static final String JSON_KEY_CREATED_AT = "created_at"; + /** * {@link URLTemplate} for {@link BoxWebHook}s resource. */ @@ -183,6 +195,32 @@ protected BoxWebHook.Info factory(JsonObject jsonObject) { }; } + /** + * Returns iterator over all {@link BoxWebHook}-s. + * + * @param api + * the API connection to be used by the resource + * @param fields + * the fields to retrieve. + * @return existing {@link BoxWebHook.Info}-s + */ + public static Iterable all(final BoxAPIConnection api, String ... fields) { + QueryStringBuilder builder = new QueryStringBuilder(); + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + return new BoxResourceIterable( + api, WEBHOOKS_URL_TEMPLATE.buildWithQuery(api.getBaseURL(), builder.toString()), 64) { + + @Override + protected BoxWebHook.Info factory(JsonObject jsonObject) { + BoxWebHook webHook = new BoxWebHook(api, jsonObject.get("id").asString()); + return webHook.new Info(jsonObject); + } + + }; + } + /** * Validates that provided {@link BoxWebHook.Trigger}-s can be applied on the provided {@link BoxResourceType}. * @@ -229,6 +267,21 @@ public BoxWebHook.Info getInfo() { return new Info(JsonObject.readFrom(response.getJSON())); } + /** + * @param fields the fields to retrieve. + * @return Gets information about this {@link BoxWebHook}. + */ + public BoxWebHook.Info getInfo(String ... fields) { + QueryStringBuilder builder = new QueryStringBuilder(); + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + URL url = WEBHOOK_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), builder.toString(), this.getID()); + BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + return new Info(JsonObject.readFrom(response.getJSON())); + } + /** * Updates {@link BoxWebHook} information. * @@ -275,6 +328,16 @@ public class Info extends BoxResource.Info { */ private Set triggers; + /** + * @see #getCreatedBy() + */ + private BoxUser.Info createdBy; + + /** + * @see #getCreatedAt() + */ + private Date createdAt; + /** * Constructs an Info object with current target. */ @@ -311,6 +374,24 @@ public Info(JsonObject jsonObject) { throw new RuntimeException(e); } } + + if (jsonObject.get(JSON_KEY_CREATED_BY) != null) { + JsonObject userJSON = jsonObject.get(JSON_KEY_CREATED_BY).asObject(); + if (this.createdBy == null) { + BoxUser user = new BoxUser(getAPI(), userJSON.get(JSON_KEY_TARGET_ID).asString()); + this.createdBy = user.new Info(userJSON); + } else { + this.createdBy.update(userJSON); + } + } + + if (jsonObject.get(JSON_KEY_CREATED_AT) != null) { + try { + this.createdAt = BoxDateFormat.parse(jsonObject.get(JSON_KEY_CREATED_AT).asString()); + } catch (ParseException e) { + assert false : "A ParseException indicates a bug in the SDK."; + } + } } /** @@ -398,6 +479,58 @@ public Info setTriggers(Set triggers) { return this; } + /** + * @return Info about the user who created this webhook. + */ + public BoxUser.Info getCreatedBy() { + return this.createdBy; + } + + /** + * @return the time this webhook was created. + */ + public Date getCreatedAt() { + return this.createdAt; + } + + /** + * {@inheritDoc} + */ + @Override + void parseJSONMember(JsonObject.Member member) { + super.parseJSONMember(member); + String memberName = member.getName(); + JsonValue value = member.getValue(); + try { + if (memberName.equals(JSON_KEY_TARGET)) { + String targetType = value.asObject().get(JSON_KEY_TARGET_TYPE).asString(); + String targetId = value.asObject().get(JSON_KEY_TARGET_ID).asString(); + this.target = new Target(targetType, targetId); + } else if (memberName.equals(JSON_KEY_TRIGGERS)) { + this.triggers = new HashSet( + CollectionUtils.map(value.asArray().values(), JSON_VALUE_TO_TRIGGER) + ); + } else if (memberName.equals(JSON_KEY_ADDRESS)) { + this.address = new URL(value.asString()); + } else if (memberName.equals(JSON_KEY_CREATED_BY)) { + JsonObject userJSON = value.asObject(); + if (this.createdBy == null) { + String userID = userJSON.get(JSON_KEY_ID).asString(); + BoxUser user = new BoxUser(getAPI(), userID); + this.createdBy = user.new Info(userJSON); + } else { + this.createdBy.update(userJSON); + } + } else if (memberName.equals("created_at")) { + this.createdAt = BoxDateFormat.parse(value.asString()); + } + } catch (ParseException e) { + assert false : "A ParseException indicates a bug in the SDK."; + } catch (MalformedURLException e) { + assert false : "A MalformedURLException indicates a bug in the SDK."; + } + } + } /** diff --git a/src/test/java/com/box/sdk/BoxWebHookTest.java b/src/test/java/com/box/sdk/BoxWebHookTest.java index 91a8b67b1..4ec8ee4db 100644 --- a/src/test/java/com/box/sdk/BoxWebHookTest.java +++ b/src/test/java/com/box/sdk/BoxWebHookTest.java @@ -3,9 +3,13 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.MalformedURLException; import java.net.URL; +import java.text.ParseException; import java.util.Arrays; +import java.util.Date; import java.util.HashSet; +import java.util.Iterator; import java.util.Set; import static org.hamcrest.Matchers.equalTo; @@ -16,11 +20,437 @@ import static org.junit.Assert.assertThat; import org.hamcrest.Matchers; +import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; +import com.eclipsesource.json.JsonArray; +import com.eclipsesource.json.JsonObject; +/** + * {@link BoxWebHook} related tests. + */ public class BoxWebHookTest { + + /** + * Unit test for {@link BoxWebHook#create(BoxResource, URL, BoxWebHook.Trigger...)} + */ + @Test + @Category(UnitTest.class) + public void testCreateSendsCorrectJson() throws MalformedURLException { + final String targetID = "1"; + final String targetType = "folder"; + final String address = "http://box.com"; + final String trigger = "FILE.UPLOADED"; + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals("https://api.box.com/2.0/webhooks", + request.getUrl().toString()); + Assert.assertEquals(targetID, json.get("target").asObject().get("id").asString()); + Assert.assertEquals(targetType, json.get("target").asObject().get("type").asString()); + Assert.assertEquals(address, json.get("address").asString()); + Assert.assertEquals(trigger, json.get("triggers").asArray().get(0).asString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + BoxWebHook.create(new BoxFolder(api, "1"), new URL(address), BoxWebHook.Trigger.FILE_UPLOADED); + } + + /** + * Unit test for {@link BoxWebHook#create(BoxResource, URL, BoxWebHook.Trigger...)} + */ + @Test + @Category(UnitTest.class) + public void testCreateParseAllFieldsCorrectly() throws ParseException, MalformedURLException { + final String id = "4165"; + final String targetID = "5016243669"; + final String targetType = "file"; + final String createdByID = "2030392653"; + final String createdByName = "John Q. Developer"; + final String createdByLogin = "johnq@dev.name"; + final Date createdAt = BoxDateFormat.parse("2016-05-09T17:41:27-07:00"); + final URL address = new URL("https://dev.name/actions/file_changed"); + final BoxWebHook.Trigger firstTrigger = BoxWebHook.Trigger.FILE_DOWNLOADED; + final BoxWebHook.Trigger secondTrigger = BoxWebHook.Trigger.FILE_UPLOADED; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"id\": \"4165\",\n" + + " \"type\": \"webhook\",\n" + + " \"target\": {\n" + + " \"id\": \"5016243669\",\n" + + " \"type\": \"file\"\n" + + " },\n" + + " \"created_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"2030392653\",\n" + + " \"name\": \"John Q. Developer\",\n" + + " \"login\": \"johnq@dev.name\"\n" + + " },\n" + + " \"created_at\": \"2016-05-09T17:41:27-07:00\",\n" + + " \"address\": \"https://dev.name/actions/file_changed\",\n" + + " \"triggers\": [\n" + + " \"FILE.DOWNLOADED\",\n" + + " \"FILE.UPLOADED\"\n" + + " ]\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxWebHook.Info info = BoxWebHook.create(new BoxFile(api, "0"), address); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(targetID, info.getTarget().getId()); + Assert.assertEquals(targetType, info.getTarget().getType()); + Assert.assertEquals(createdByID, info.getCreatedBy().getID()); + Assert.assertEquals(createdByName, info.getCreatedBy().getName()); + Assert.assertEquals(createdByLogin, info.getCreatedBy().getLogin()); + Assert.assertEquals(createdAt, info.getCreatedAt()); + Assert.assertEquals(address, info.getAddress()); + Assert.assertEquals(true, info.getTriggers().contains(firstTrigger)); + Assert.assertEquals(true, info.getTriggers().contains(secondTrigger)); + + } + + /** + * Unit test for {@link BoxWebHook#getInfo())} + */ + @Test + @Category(UnitTest.class) + public void testGetInfoSendsCorrectRequestWithoutFields() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/webhooks/0", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + BoxWebHook hook = new BoxWebHook(api, "0"); + hook.getInfo(); + } + + /** + * Unit test for {@link BoxWebHook#getInfo(String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetInfoSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/webhooks/0?fields=created_at", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + BoxWebHook hook = new BoxWebHook(api, "0"); + hook.getInfo("created_at"); + } + + /** + * Unit test for {@link BoxWebHook#getInfo()} + */ + @Test + @Category(UnitTest.class) + public void testGetInfoParseAllFieldsCorrectly() throws ParseException, MalformedURLException { + final String id = "4137"; + final String targetID = "5018848529"; + final String targetType = "file"; + final String createdByID = "2030392653"; + final String createdByName = "John Q. Developer"; + final String createdByLogin = "johnq@example.net"; + final Date createdAt = BoxDateFormat.parse("2016-05-04T18:51:45-07:00"); + final URL address = new URL("https://example.net/actions/file_changed"); + final BoxWebHook.Trigger trigger = BoxWebHook.Trigger.FILE_PREVIEWED; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"id\": \"4137\",\n" + + " \"type\": \"webhook\",\n" + + " \"target\": {\n" + + " \"id\": \"5018848529\",\n" + + " \"type\": \"file\"\n" + + " },\n" + + " \"created_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"2030392653\",\n" + + " \"name\": \"John Q. Developer\",\n" + + " \"login\": \"johnq@example.net\"\n" + + " },\n" + + " \"created_at\": \"2016-05-04T18:51:45-07:00\",\n" + + " \"address\": \"https://example.net/actions/file_changed\",\n" + + " \"triggers\": [\n" + + " \"FILE.PREVIEWED\"\n" + + " ]\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxWebHook.Info info = new BoxWebHook(api, id).getInfo(); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(targetID, info.getTarget().getId()); + Assert.assertEquals(targetType, info.getTarget().getType()); + Assert.assertEquals(createdByID, info.getCreatedBy().getID()); + Assert.assertEquals(createdByName, info.getCreatedBy().getName()); + Assert.assertEquals(createdByLogin, info.getCreatedBy().getLogin()); + Assert.assertEquals(createdAt, info.getCreatedAt()); + Assert.assertEquals(address, info.getAddress()); + Assert.assertEquals(trigger, info.getTriggers().toArray()[0]); + } + + /** + * Unit test for {@link BoxWebHook#delete()} + */ + @Test + @Category(UnitTest.class) + public void testDeleteSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/webhooks/0", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + BoxWebHook hook = new BoxWebHook(api, "0"); + hook.delete(); + } + + /** + * Unit test for {@link BoxWebHook#all(BoxAPIConnection)} + */ + @Test + @Category(UnitTest.class) + public void testAllSendsCorrectRequestWithoutParams() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/webhooks?limit=64", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"entries\": []}"; + } + }; + } + }); + + Iterator iterator = BoxWebHook.all(api).iterator(); + iterator.hasNext(); + } + + /** + * Unit test for {@link BoxWebHook#all(BoxAPIConnection, String...)} + */ + @Test + @Category(UnitTest.class) + public void testAllSendsCorrectRequestWithFields() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/webhooks?fields=created_at%2Ccreated_by&limit=64", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"entries\": []}"; + } + }; + } + }); + + Iterator iterator = BoxWebHook.all(api, "created_at", "created_by").iterator(); + iterator.hasNext(); + } + + /** + * Unit test for {@link BoxWebHook#all(BoxAPIConnection)} + */ + @Test + @Category(UnitTest.class) + public void testAllParseAllFieldsCorrectly() { + final String firstID = "4161"; + final String firstTargetID = "5018326685"; + final String firstTargetType = "folder"; + final String secondID = "4165"; + final String secondTargetID = "5016243669"; + final String secondTargerType = "file"; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"entries\": [\n" + + " {\n" + + " \"id\": \"4161\",\n" + + " \"type\": \"webhook\",\n" + + " \"target\": {\n" + + " \"id\": \"5018326685\",\n" + + " \"type\": \"folder\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"id\": \"4165\",\n" + + " \"type\": \"webhook\",\n" + + " \"target\": {\n" + + " \"id\": \"5016243669\",\n" + + " \"type\": \"file\"\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"limit\": 3\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + Iterator iterator = BoxWebHook.all(api).iterator(); + BoxWebHook.Info info = iterator.next(); + Assert.assertEquals(firstID, info.getID()); + Assert.assertEquals(firstTargetID, info.getTarget().getId()); + Assert.assertEquals(firstTargetType, info.getTarget().getType()); + info = iterator.next(); + Assert.assertEquals(secondID, info.getID()); + Assert.assertEquals(secondTargetID, info.getTarget().getId()); + Assert.assertEquals(secondTargerType, info.getTarget().getType()); + Assert.assertEquals(false, iterator.hasNext()); + + } + + /** + * Unit test for {@link BoxWebHook#updateInfo(BoxWebHook.Info)} + */ + @Test + @Category(UnitTest.class) + public void testUpdateSendCorrectJSON() { + final String address = ""; + final String firstTrigger = "FILE.PREVIEWED"; + final String secondTrigger = "FILE.DOWNLOADED"; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"id\": \"4137\",\n" + + " \"type\": \"webhook\",\n" + + " \"target\": {\n" + + " \"id\": \"5018848529\",\n" + + " \"type\": \"file\"\n" + + " }\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxWebHook hook = new BoxWebHook(api, "0"); + BoxWebHook.Info info = hook.new Info(); + + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals("https://api.box.com/2.0/webhooks/0", + request.getUrl().toString()); + Assert.assertEquals(address, json.get("address").asString()); + Assert.assertEquals(firstTrigger, json.get("triggers").asArray().get(0).asString()); + Assert.assertEquals(secondTrigger, json.get("triggers").asArray().get(1).asString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + info.addPendingChange("address", address); + info.addPendingChange("triggers", new JsonArray().add(firstTrigger).add(secondTrigger)); + hook.updateInfo(info); + } + + /** + * Unit test for {@link BoxWebHook#updateInfo(BoxWebHook.Info)} + */ + @Test + @Category(UnitTest.class) + public void testUpdateParseAllFieldsCorrectly() throws ParseException, MalformedURLException { + final String id = "4133"; + final String targetID = "1000605797"; + final String targetType = "folder"; + final String createdByID = "2030392653"; + final String createdByName = "John Q. Developer"; + final String createdByLogin = "john2@example.net"; + final Date createdAt = BoxDateFormat.parse("2016-05-04T18:51:17-07:00"); + final URL address = new URL("https://notification.example.net"); + final BoxWebHook.Trigger firstTrigger = BoxWebHook.Trigger.FILE_PREVIEWED; + final BoxWebHook.Trigger secondTrigger = BoxWebHook.Trigger.FILE_DOWNLOADED; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"id\": \"4133\",\n" + + " \"type\": \"webhook\",\n" + + " \"target\": {\n" + + " \"id\": \"1000605797\",\n" + + " \"type\": \"folder\"\n" + + " },\n" + + " \"created_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"2030392653\",\n" + + " \"name\": \"John Q. Developer\",\n" + + " \"login\": \"john2@example.net\"\n" + + " },\n" + + " \"created_at\": \"2016-05-04T18:51:17-07:00\",\n" + + " \"address\": \"https://notification.example.net\",\n" + + " \"triggers\": [\n" + + " \"FILE.PREVIEWED\", \"FILE.DOWNLOADED\"\n" + + " ]\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxWebHook hook = new BoxWebHook(api, id); + BoxWebHook.Info info = hook.new Info(); + info.addPendingChange("address", "fake pending change"); + hook.updateInfo(info); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(targetID, info.getTarget().getId()); + Assert.assertEquals(targetType, info.getTarget().getType()); + Assert.assertEquals(createdByID, info.getCreatedBy().getID()); + Assert.assertEquals(createdByName, info.getCreatedBy().getName()); + Assert.assertEquals(createdByLogin, info.getCreatedBy().getLogin()); + Assert.assertEquals(createdAt, info.getCreatedAt()); + Assert.assertEquals(address, info.getAddress()); + Assert.assertEquals(true, info.getTriggers().contains(firstTrigger)); + Assert.assertEquals(true, info.getTriggers().contains(secondTrigger)); + + } + @Test @Category(IntegrationTest.class) public void createWebHookFileSucceeds() throws IOException { From 119264dfa3ca73e374e56fc2cabe9f0026f8cb4e Mon Sep 17 00:00:00 2001 From: gleb-urvanov Date: Mon, 14 Nov 2016 13:27:14 +0100 Subject: [PATCH 021/119] get metadata template and get enterprise metadata features implementation --- README.md | 1 + doc/metadata_template.md | 41 ++ src/main/java/com/box/sdk/BoxFile.java | 3 + .../java/com/box/sdk/MetadataTemplate.java | 390 ++++++++++++++++++ .../com/box/sdk/MetadataTemplateTest.java | 252 +++++++++++ 5 files changed, 687 insertions(+) create mode 100644 doc/metadata_template.md create mode 100644 src/main/java/com/box/sdk/MetadataTemplate.java create mode 100644 src/test/java/com/box/sdk/MetadataTemplateTest.java diff --git a/README.md b/README.md index 4344725bd..ccd5ddba4 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,7 @@ You can find guides and tutorials in the `doc` directory. * [Devices](doc/devices.md) * [Retention Policies](doc/retention_policies.md) * [Legal Holds Policy](doc/legal_holds.md) +* [Metadata Templates](doc/metadata_template.md) Javadocs are generated when `gradle javadoc` is run and can be found in `build/doc/javadoc`. diff --git a/doc/metadata_template.md b/doc/metadata_template.md new file mode 100644 index 000000000..7b9677b7b --- /dev/null +++ b/doc/metadata_template.md @@ -0,0 +1,41 @@ +Metadata Templates +================== + +Metadata that belongs to a file is grouped by templates. Templates allow the metadata service to provide a multitude of services, such as pre-defining sets of key:value pairs or schema enforcement on specific fields. + +* [Get Metadata Template](#get-metadata-template) +* [Get Enterprise Metadata Templates](#get-enterprise-metadata-templates) + + +Get Metadata Template +-------------------- + +The [`getMetadataTemplate(BoxAPIConnection)`][get-metadata-template-1] method will return information about default metadata schema. +Also [`getMetadataTemplate(BoxAPIConnection, String)`][get-metadata-template-2] and [`getMetadataTemplate(BoxAPIConnection, String, String, String...)`][get-metadata-template-3] can be used to set metadata template name, metadata scope and fields to retrieve. + +```java +MetadataTemplate template = MetadataTemplate.getMetadataTemplate(api, "templateName"); +``` + +[get-metadata-template-1]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/MetadataTemplate.html#getEnterpriseMetadataTemplates(com.box.sdk.BoxAPIConnection) +[get-metadata-template-2]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/MetadataTemplate.html#getEnterpriseMetadataTemplates(com.box.sdk.BoxAPIConnection,%20java.lang.String) +[get-metadata-template-3]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/MetadataTemplate.html#getEnterpriseMetadataTemplates(com.box.sdk.BoxAPIConnection,%20java.lang.String,%20java.lang.String,%20java.lang.String...) + + +Get Enterprise Metadata Templates +--------------------------------- + +Calling the static [`getEnterpriseMetadataTemplates(BoxAPIConnection, String...)`][get-enterprise-metadata-1] will +return an iterable that will page through all metadata templates within a user's enterprise. +Also [`getEnterpriseMetadataTemplates(String, BoxAPIConnection, String...)`][get-enterprise-metadata-2] and [`getEnterpriseMetadataTemplates(String, int, BoxAPIConnection, String...)`][get-enterprise-metadata-3] can be used to set metadata scope, limit of items per single response. + +```java +Iterable templates = MetadataTemplate.getEnterpriseMetadataTemplates(BoxAPIConnection api); +for (MetadataTemplate templateInfo : templates) { + // Do something with the metadata template. +} +``` + +[get-enterprise-metadata-1]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/MetadataTemplate.html#getEnterpriseMetadataTemplates(com.box.sdk.BoxAPIConnection,%20java.lang.String...) +[get-enterprise-metadata-2]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/MetadataTemplate.html#getEnterpriseMetadataTemplates(java.lang.String,%20com.box.sdk.BoxAPIConnection,%20java.lang.String...) +[get-enterprise-metadata-3]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/MetadataTemplate.html#getEnterpriseMetadataTemplates(java.lang.String,%20int,%20com.box.sdk.BoxAPIConnection,%20java.lang.String...) \ No newline at end of file diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 8530ac0ae..48a75693b 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -758,6 +758,9 @@ public void deleteMetadata(String typeName, String scope) { request.send(); } + /** + * {@inheritDoc} + */ @Override public BoxFile.Info setCollections(BoxCollection... collections) { JsonArray jsonArray = new JsonArray(); diff --git a/src/main/java/com/box/sdk/MetadataTemplate.java b/src/main/java/com/box/sdk/MetadataTemplate.java new file mode 100644 index 000000000..518606d6d --- /dev/null +++ b/src/main/java/com/box/sdk/MetadataTemplate.java @@ -0,0 +1,390 @@ +package com.box.sdk; + +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import com.eclipsesource.json.JsonObject; +import com.eclipsesource.json.JsonValue; + +/** + * The MetadataTemplate class represents the Box metadata template object. + * Templates allow the metadata service to provide a multitude of services, + * such as pre-defining sets of key:value pairs or schema enforcement on specific fields. + * + * @see Box metadata templates + */ +public class MetadataTemplate extends BoxJSONObject { + + /** + * @see #getMetadataTemplate(BoxAPIConnection) + */ + private static final URLTemplate METADATA_TEMPLATE_URL_TEMPLATE + = new URLTemplate("metadata_templates/%s/%s/schema"); + + /** + * @see #getEnterpriseMetadataTemplates(String, int, BoxAPIConnection, String...) + */ + private static final URLTemplate ENTERPRISE_METADATA_URL_TEMPLATE = new URLTemplate("metadata_templates/%s"); + + /** + * Default metadata type to be used in query. + */ + private static final String DEFAULT_METADATA_TYPE = "properties"; + + /** + * Global metadata scope. Used by default if the metadata type is "properties". + */ + private static final String GLOBAL_METADATA_SCOPE = "global"; + + /** + * Enterprise metadata scope. Used by default if the metadata type is not "properties". + */ + private static final String ENTERPRISE_METADATA_SCOPE = "enterprise"; + + /** + * Default number of entries per page. + */ + private static final int DEFAULT_ENTRIES_LIMIT = 100; + + /** + * @see #getTemplateKey() + */ + private String templateKey; + + /** + * @see #getScope() + */ + private String scope; + + /** + * @see #getDisplayName() + */ + private String displayName; + + /** + * @see #getIsHidden() + */ + private Boolean isHidden; + + /** + * @see #getFields() + */ + private List fields; + + /** + * Constructs an empty metadata template. + */ + public MetadataTemplate() { + super(); + } + + /** + * Constructs a metadata template from a JSON string. + * @param json the json encoded metadate template. + */ + public MetadataTemplate(String json) { + super(json); + } + + /** + * Constructs a metadate template from a JSON object. + * @param jsonObject the json encoded metadate template. + */ + MetadataTemplate(JsonObject jsonObject) { + super(jsonObject); + } + + /** + * Gets the unique template key to identify the metadata template. + * @return the unique template key to identify the metadata template. + */ + public String getTemplateKey() { + return this.templateKey; + } + + /** + * Gets the metadata template scope. + * @return the metadata template scope. + */ + public String getScope() { + return this.scope; + } + + /** + * Gets the displayed metadata template name. + * @return the displayed metadata template name. + */ + public String getDisplayName() { + return this.displayName; + } + + /** + * Gets is the metadata template hidden. + * @return is the metadata template hidden. + */ + public Boolean getIsHidden() { + return this.isHidden; + } + + /** + * Gets the iterable with all fields the metadata template contains. + * @return the iterable with all fields the metadata template contains. + */ + public List getFields() { + return this.fields; + } + + /** + * {@inheritDoc} + */ + @Override + void parseJSONMember(JsonObject.Member member) { + JsonValue value = member.getValue(); + String memberName = member.getName(); + if (memberName.equals("templateKey")) { + this.templateKey = value.asString(); + } else if (memberName.equals("scope")) { + this.scope = value.asString(); + } else if (memberName.equals("displayName")) { + this.displayName = value.asString(); + } else if (memberName.equals("hidden")) { + this.isHidden = value.asBoolean(); + } else if (memberName.equals("fields")) { + this.fields = new ArrayList(); + for (JsonValue field: value.asArray()) { + this.fields.add(new Field(field.asObject())); + } + } + } + + /** + * Gets the metadata template of properties. + * @param api the API connection to be used. + * @return the metadata template returned from the server. + */ + public static MetadataTemplate getMetadataTemplate(BoxAPIConnection api) { + return getMetadataTemplate(api, DEFAULT_METADATA_TYPE); + } + + /** + * Gets the metadata template of specified template type. + * @param api the API connection to be used. + * @param templateName the metadata template type name. + * @return the metadata template returned from the server. + */ + public static MetadataTemplate getMetadataTemplate(BoxAPIConnection api, String templateName) { + String scope = scopeBasedOnType(templateName); + return getMetadataTemplate(api, templateName, scope); + } + + /** + * Gets the metadata template of specified template type. + * @param api the API connection to be used. + * @param templateName the metadata template type name. + * @param scope the metadata template scope (global or enterprise). + * @param fields the fields to retrieve. + * @return the metadata template returned from the server. + */ + public static MetadataTemplate getMetadataTemplate( + BoxAPIConnection api, String templateName, String scope, String ... fields) { + QueryStringBuilder builder = new QueryStringBuilder(); + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + URL url = METADATA_TEMPLATE_URL_TEMPLATE.buildWithQuery( + api.getBaseURL(), builder.toString(), scope, templateName); + BoxAPIRequest request = new BoxAPIRequest(api, url, "GET"); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + return new MetadataTemplate(response.getJSON()); + } + + /** + * Returns all metadata templates within a user's enterprise. + * @param api the API connection to be used. + * @param fields the fields to retrieve. + * @return the metadata template returned from the server. + */ + public static Iterable getEnterpriseMetadataTemplates(BoxAPIConnection api, String ... fields) { + return getEnterpriseMetadataTemplates(ENTERPRISE_METADATA_SCOPE, api, fields); + } + + /** + * Returns all metadata templates within a user's scope. Currently only the enterprise scope is supported. + * @param scope the scope of the metadata templates. + * @param api the API connection to be used. + * @param fields the fields to retrieve. + * @return the metadata template returned from the server. + */ + public static Iterable getEnterpriseMetadataTemplates( + String scope, BoxAPIConnection api, String ... fields) { + return getEnterpriseMetadataTemplates(ENTERPRISE_METADATA_SCOPE, DEFAULT_ENTRIES_LIMIT, api, fields); + } + + /** + * Returns all metadata templates within a user's scope. Currently only the enterprise scope is supported. + * @param scope the scope of the metadata templates. + * @param limit maximum number of entries per response. + * @param api the API connection to be used. + * @param fields the fields to retrieve. + * @return the metadata template returned from the server. + */ + public static Iterable getEnterpriseMetadataTemplates( + String scope, int limit, BoxAPIConnection api, String ... fields) { + QueryStringBuilder builder = new QueryStringBuilder(); + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + return new BoxResourceIterable( + api, ENTERPRISE_METADATA_URL_TEMPLATE.buildWithQuery( + api.getBaseURL(), builder.toString(), scope), limit) { + + @Override + protected MetadataTemplate factory(JsonObject jsonObject) { + return new MetadataTemplate(jsonObject); + } + }; + } + + /** + * Determines the metadata scope based on type. + * @param typeName type of the metadata. + * @return scope of the metadata. + */ + private static String scopeBasedOnType(String typeName) { + return typeName.equals(DEFAULT_METADATA_TYPE) ? GLOBAL_METADATA_SCOPE : ENTERPRISE_METADATA_SCOPE; + } + + /** + * Class contains information about the metadata template field. + */ + public class Field extends BoxJSONObject { + + /** + * @see #getType() + */ + private String type; + + /** + * @see #getKey() + */ + private String key; + + /** + * @see #getDisplayName() + */ + private String displayName; + + /** + * @see #getIsHidden() + */ + private Boolean isHidden; + + /** + * @see #getDescription() + */ + private String description; + + /** + * @see #getOptions() + */ + private List options; + + /** + * Constructs an empty metadata template. + */ + public Field() { + super(); + } + + /** + * Constructs a metadate template field from a JSON string. + * @param json the json encoded metadate template field. + */ + public Field(String json) { + super(json); + } + + /** + * Constructs a metadate template field from a JSON object. + * @param jsonObject the json encoded metadate template field. + */ + Field(JsonObject jsonObject) { + super(jsonObject); + } + + /** + * Gets the data type of the field's value. + * @return the data type of the field's value. + */ + public String getType() { + return this.type; + } + + /** + * Gets the key of the field. + * @return the key of the field. + */ + public String getKey() { + return this.key; + } + + /** + * Gets the display name of the field. + * @return the display name of the field. + */ + public String getDisplayName() { + return this.displayName; + } + + /** + * Gets is metadata template field hidden. + * @return is metadata template field hidden. + */ + public Boolean getIsHidden() { + return this.isHidden; + } + + /** + * Gets the description of the field. + * @return the description of the field. + */ + public String getDescription() { + return this.description; + } + + /** + * Gets list of possible options for enum type of the field. + * @return list of possible options for enum type of the field. + */ + public List getOptions() { + return this.options; + } + + /** + * {@inheritDoc} + */ + @Override + void parseJSONMember(JsonObject.Member member) { + JsonValue value = member.getValue(); + String memberName = member.getName(); + if (memberName.equals("type")) { + this.type = value.asString(); + } else if (memberName.equals("key")) { + this.key = value.asString(); + } else if (memberName.equals("displayName")) { + this.displayName = value.asString(); + } else if (memberName.equals("hidden")) { + this.isHidden = value.asBoolean(); + } else if (memberName.equals("description")) { + this.description = value.asString(); + } else if (memberName.equals("options")) { + this.options = new ArrayList(); + for (JsonValue key: value.asArray()) { + this.options.add(key.asObject().get("key").asString()); + } + } + } + } + +} diff --git a/src/test/java/com/box/sdk/MetadataTemplateTest.java b/src/test/java/com/box/sdk/MetadataTemplateTest.java new file mode 100644 index 000000000..8ca7c60f6 --- /dev/null +++ b/src/test/java/com/box/sdk/MetadataTemplateTest.java @@ -0,0 +1,252 @@ +package com.box.sdk; + +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import com.eclipsesource.json.JsonObject; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.junit.WireMockRule; + +/** + * {@link MetadataTemplate} related unit tests. + */ +public class MetadataTemplateTest { + + /** + * Wiremock + */ + @Rule + public final WireMockRule wireMockRule = new WireMockRule(8080); + + /** + * Unit test for {@link MetadataTemplate#getMetadataTemplate(BoxAPIConnection, String, String, String...)}. + */ + @Test + @Category(UnitTest.class) + public void testGetMetadataTemplateSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals( + "https://api.box.com/2.0/metadata_templates/global/properties/schema" + + "?fields=displayName%2Chidden", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + MetadataTemplate.getMetadataTemplate(api, "properties", "global", "displayName", "hidden"); + } + + /** + * Unit test for {@link MetadataTemplate#getMetadataTemplate(BoxAPIConnection)}. + */ + @Test + @Category(UnitTest.class) + public void testGetMetadataTemplateParseAllFieldsCorrectly() { + final String templateKey = "productInfo"; + final String scope = "enterprise_12345"; + final String displayName = "Product Info"; + final Boolean isHidden = false; + final String firstFieldType = "float"; + final String firstFieldKey = "skuNumber"; + final String firstFieldDisplayName = "SKU Number"; + final Boolean firstFieldIsHidden = false; + final String secondFieldType = "enum"; + final String secondFieldKey = "department"; + final String secondFieldDisplayName = "Department"; + final Boolean secondFieldIsHidden = false; + final String secondFieldFirstOption = "Beauty"; + final String secondFieldSecondOption = "Accessories"; + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setBaseURL("http://localhost:8080/"); + WireMock.stubFor(WireMock.get(WireMock.urlMatching("/metadata_templates/global/properties/schema")) + .willReturn(WireMock.aResponse() + .withHeader("Content-Type", "application/json") + .withBody("{\n" + + " \"templateKey\": \"productInfo\",\n" + + " \"scope\": \"enterprise_12345\",\n" + + " \"displayName\": \"Product Info\",\n" + + " \"hidden\": false,\n" + + " \"fields\": [\n" + + " {\n" + + " \"type\": \"float\",\n" + + " \"key\": \"skuNumber\",\n" + + " \"displayName\": \"SKU Number\",\n" + + " \"hidden\": false\n" + + " },\n" + + " {\n" + + " \"type\": \"enum\",\n" + + " \"key\": \"department\",\n" + + " \"displayName\": \"Department\",\n" + + " \"hidden\": false,\n" + + " \"options\": [\n" + + " {\n" + + " \"key\": \"Beauty\"\n" + + " },\n" + + " {\n" + + " \"key\": \"Accessories\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"))); + + MetadataTemplate template = MetadataTemplate.getMetadataTemplate(api); + Assert.assertEquals(templateKey, template.getTemplateKey()); + Assert.assertEquals(scope, template.getScope()); + Assert.assertEquals(displayName, template.getDisplayName()); + Assert.assertEquals(isHidden, template.getIsHidden()); + List templateFields = template.getFields(); + Assert.assertEquals(firstFieldType, templateFields.get(0).getType()); + Assert.assertEquals(firstFieldKey, templateFields.get(0).getKey()); + Assert.assertEquals(firstFieldDisplayName, templateFields.get(0).getDisplayName()); + Assert.assertEquals(firstFieldIsHidden, templateFields.get(0).getIsHidden()); + Assert.assertEquals(secondFieldType, templateFields.get(1).getType()); + Assert.assertEquals(secondFieldKey, templateFields.get(1).getKey()); + Assert.assertEquals(secondFieldDisplayName, templateFields.get(1).getDisplayName()); + Assert.assertEquals(secondFieldIsHidden, templateFields.get(1).getIsHidden()); + Assert.assertEquals(secondFieldFirstOption, templateFields.get(1).getOptions().get(0)); + Assert.assertEquals(secondFieldSecondOption, templateFields.get(1).getOptions().get(1)); + + } + + /** + * Unit test for {@link MetadataTemplate#getEnterpriseMetadataTemplates(BoxAPIConnection, String...)}. + */ + @Test(expected = NoSuchElementException.class) + @Category(UnitTest.class) + public void testGetEnterpriseMetadataTemplatesSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals( + "https://api.box.com/2.0/metadata_templates/enterprise?fields=displayName%2Chidden&limit=100", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"entries\":[]}"; + } + }; + } + }); + + Iterator iterator = + MetadataTemplate.getEnterpriseMetadataTemplates(api, "displayName", "hidden").iterator(); + iterator.next(); + } + + /** + * Unit test for {@link MetadataTemplate#getEnterpriseMetadataTemplates(BoxAPIConnection, String...)}. + */ + @Test + @Category(UnitTest.class) + public void testGetEnterpriseMetadataTemplatesParseAllFieldsCorrectly() { + final String firstEntryTemplateKey = "documentFlow"; + final String firstEntryScope = "enterprise_12345"; + final String firstEntryDisplayName = "Document Flow"; + final Boolean firstEntryIsHidden = false; + final String firstEntryFieldType = "string"; + final String firstEntryFieldKey = "currentDocumentStage"; + final String firstEntryFieldDisplayName = "Current Document Stage"; + final Boolean firstEntryFieldIsHidden = false; + final String firstEntryFieldDescription = "What stage in the process the document is in"; + final String secondEntryTemplateKey = "productInfo"; + final String secondEntryScope = "enterprise_12345"; + final String secondEntryDisplayName = "Product Info"; + final Boolean secondEntryIsHidden = false; + final String secondEntryFieldType = "enum"; + final String secondEntryFieldKey = "department"; + final String secondEntryFieldDisplayName = "Department"; + final Boolean secondEntryFieldIsHidden = false; + final String secondEntryFieldFirstOption = "Beauty"; + final String secondEntryFieldSecondOption = "Shoes"; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"limit\": 100,\n" + + " \"entries\": [\n" + + " {\n" + + " \"templateKey\": \"documentFlow\",\n" + + " \"scope\": \"enterprise_12345\",\n" + + " \"displayName\": \"Document Flow\",\n" + + " \"hidden\": false,\n" + + " \"fields\": [\n" + + " {\n" + + " \"type\": \"string\",\n" + + " \"key\": \"currentDocumentStage\",\n" + + " \"displayName\": \"Current Document Stage\",\n" + + " \"hidden\": false,\n" + + " \"description\": \"What stage in the process the document is in\"\n" + + " }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"templateKey\": \"productInfo\",\n" + + " \"scope\": \"enterprise_12345\",\n" + + " \"displayName\": \"Product Info\",\n" + + " \"hidden\": false,\n" + + " \"fields\": [\n" + + " {\n" + + " \"type\": \"enum\",\n" + + " \"key\": \"department\",\n" + + " \"displayName\": \"Department\",\n" + + " \"hidden\": false,\n" + + " \"options\": [\n" + + " {\n" + + " \"key\": \"Beauty\"\n" + + " },\n" + + " {\n" + + " \"key\": \"Shoes\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"next_marker\": null,\n" + + " \"prev_marker\": null\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + Iterator iterator = MetadataTemplate.getEnterpriseMetadataTemplates(api).iterator(); + MetadataTemplate template = iterator.next(); + Assert.assertEquals(firstEntryTemplateKey, template.getTemplateKey()); + Assert.assertEquals(firstEntryScope, template.getScope()); + Assert.assertEquals(firstEntryDisplayName, template.getDisplayName()); + Assert.assertEquals(firstEntryIsHidden, template.getIsHidden()); + Assert.assertEquals(firstEntryFieldType, template.getFields().get(0).getType()); + Assert.assertEquals(firstEntryFieldKey, template.getFields().get(0).getKey()); + Assert.assertEquals(firstEntryFieldDisplayName, template.getFields().get(0).getDisplayName()); + Assert.assertEquals(firstEntryFieldIsHidden, template.getFields().get(0).getIsHidden()); + Assert.assertEquals(firstEntryFieldDescription, template.getFields().get(0).getDescription()); + template = iterator.next(); + Assert.assertEquals(secondEntryTemplateKey, template.getTemplateKey()); + Assert.assertEquals(secondEntryScope, template.getScope()); + Assert.assertEquals(secondEntryDisplayName, template.getDisplayName()); + Assert.assertEquals(secondEntryIsHidden, template.getIsHidden()); + Assert.assertEquals(secondEntryFieldType, template.getFields().get(0).getType()); + Assert.assertEquals(secondEntryFieldKey, template.getFields().get(0).getKey()); + Assert.assertEquals(secondEntryFieldDisplayName, template.getFields().get(0).getDisplayName()); + Assert.assertEquals(secondEntryFieldIsHidden, template.getFields().get(0).getIsHidden()); + Assert.assertEquals(secondEntryFieldFirstOption, template.getFields().get(0).getOptions().get(0)); + Assert.assertEquals(secondEntryFieldSecondOption, template.getFields().get(0).getOptions().get(1)); + Assert.assertFalse(iterator.hasNext()); + } +} From b2aac578c563168254d44de84332717b760068ff Mon Sep 17 00:00:00 2001 From: gleb-urvanov Date: Fri, 18 Nov 2016 11:29:32 +0100 Subject: [PATCH 022/119] legal holds assignments and file version legal holds features implemented --- doc/legal_holds.md | 95 +++++++ src/main/java/com/box/sdk/BoxFileVersion.java | 17 +- .../com/box/sdk/BoxFileVersionLegalHold.java | 185 ++++++++++++ src/main/java/com/box/sdk/BoxLegalHold.java | 91 ++++++ .../com/box/sdk/BoxLegalHoldAssignment.java | 265 ++++++++++++++++++ src/main/java/com/box/sdk/BoxResource.java | 2 + .../box/sdk/BoxFileVersionLegalHoldTest.java | 97 +++++++ .../box/sdk/BoxLegalHoldAssignmentTest.java | 208 ++++++++++++++ .../java/com/box/sdk/BoxLegalHoldTest.java | 200 +++++++++++++ 9 files changed, 1159 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/box/sdk/BoxFileVersionLegalHold.java create mode 100644 src/main/java/com/box/sdk/BoxLegalHoldAssignment.java create mode 100644 src/test/java/com/box/sdk/BoxFileVersionLegalHoldTest.java create mode 100644 src/test/java/com/box/sdk/BoxLegalHoldAssignmentTest.java diff --git a/doc/legal_holds.md b/doc/legal_holds.md index 82391f0a8..d5c5c6408 100644 --- a/doc/legal_holds.md +++ b/doc/legal_holds.md @@ -9,6 +9,12 @@ such as name, description, and filter dates. * [Create New Legal Hold Policy](#create-new-legal-hold-policy) * [Update Existing Legal Hold Policy](#update-existing-legal-hold-policy) * [Delete Legal Hold Policy](#delete-legal-hold-policy) +* [Get Assignment](#get-assignment) +* [Get List of Assignments](#get-list-of-assignments) +* [Create New Assignment](#create-new-assignment) +* [Delete Assignment](#delete-assignment) +* [Get File Version Legal Hold](#get-file-version-legal-hold) +* [Get List of File Version Legal Holds](#get-list-of-file-version-legal-holds) Get Legal Hold Policy --------------------- @@ -85,3 +91,92 @@ policy.delete(); ``` [delete]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHold.html#delete() + +Get Assignment +-------------- + +Calling [`getInfo(String...)`][get-assignment] will return a BoxLegalHoldAssignment.Info +object containing information about the legal hold policy assignment. + +```java +BoxLegalHoldAssignment assignment = new BoxLegalHoldAssignment(api, id); +BoxLegalHoldAssignment.Info info = assignment.getInfo("assigned_by"); +``` + +[get-assignment]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHoldAssignment.html#getInfo(java.lang.String...) + +Get List of Assignments +-------------- + +Calling the static [`getAssignments(String...)`][get-list-of-assignments] will return +an iterable that will page through all of the assignments of the legal hold policy. +It is possible to specify filters for type and id, maximum number of items per single +response and fields to retrieve by calling [`getAssignments(String, String, int, String...)`][get-list-of-assignments-with-params]. + +```java +BoxLegalHold policy = new BoxLegalHold(api, id); +Iterable assignments = policy.getAssignments(BoxResource.getResourceType(BoxFolder.class), null, 50, "assigned_at"); +for (BoxLegalHoldAssignment.Info assignmentInfo : assignments) { + // Do something with the legal hold policy assignment. +} +``` + +[get-list-of-assignments]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHold.html#getAssignments(java.lang.String...) +[get-list-of-assignments-with-params]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHold.html#getAssignments(java.lang.String,%20java.lang.String,%20int,%20java.lang.String...) + +Create New Assignment +-------------- + +To create new legal hold policy assignment call [`assignTo(BoxResource)`][create-assignment] method. +Currently only BoxFile, BoxFileVersion, BoxFolder and BoxUser objects are supported as a parameter. + +```java +BoxLegalHold policy = new BoxLegalHold(api, policyID); +BoxFolder folder = new BoxFolder(api, folderID); +policy.assignTo(folder); +``` + +[create-assignment]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHold.html#assignTo(com.box.sdk.BoxResource) + +Delete Assignment +-------------- + +A legal hold policy assignment can be deleted by calling the [`delete()`][delete-assignment] method +of BoxLegalHoldAssignment object. + +```java +BoxLegalHoldAssignment assignment = new BoxLegalHoldAssignment(api, id); +assignment.delete(); +``` + +[delete-assignment]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHoldAssignment.html#delete() + +Get File Version Legal Hold +-------------- + +Calling [`getInfo(String...)`][get-file-version-legal-hold] will return +a BoxFileVersionLegalHold.Info object containing information about the file version legal hold policy. + +```java +BoxFileVersionLegalHold hold = new BoxFileVersionLegalHold(api, id); +hold.getInfo("file"); +``` + +[get-file-version-legal-hold]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFileVersionLegalHold.html#getInfo(java.lang.String...) + +Get List of File Version Legal Holds +-------------- +To get an iterable with all non-deleted file version legal holds for current +legal hold policy, call [`getFileVersionHolds(String...)`][get-lest-of-file-version-legal-holds]. +It is possible to specify maximum number of items per single response by calling [`getFileVersionHolds(int, String...)`][get-lest-of-file-version-legal-holds-with-limit]. + +```java +BoxLegalHold policy = new BoxLegalHold(api, id); +Iterable fileVersionHolds = policy.getFileVersionHolds(); +for (BoxFileVersionLegalHold.Info fileVersionHold : fileVersionHolds) { + // Do something with the file version legal hold. +} +``` + +[get-lest-of-file-version-legal-holds]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHold.html#getFileVersionHolds(java.lang.String...) +[get-lest-of-file-version-legal-holds-with-limit]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHold.html#getFileVersionHolds(int,%20java.lang.String...) \ No newline at end of file diff --git a/src/main/java/com/box/sdk/BoxFileVersion.java b/src/main/java/com/box/sdk/BoxFileVersion.java index 27d3832b6..dcd89b5c9 100644 --- a/src/main/java/com/box/sdk/BoxFileVersion.java +++ b/src/main/java/com/box/sdk/BoxFileVersion.java @@ -19,7 +19,7 @@ public class BoxFileVersion extends BoxResource { private static final URLTemplate VERSION_URL_TEMPLATE = new URLTemplate("files/%s/versions/%s"); private static final int BUFFER_SIZE = 8192; - private final String fileID; + private String fileID; private String versionID; private String sha1; @@ -78,6 +78,21 @@ public BoxFileVersion(BoxAPIConnection api, String json, String fileID) { } } + /** + * Used if no or wrong file id was set with constructor. + * @param fileID the file id this file version belongs to. + */ + public void setFileID(String fileID) { + this.fileID = fileID; + } + + /** + * @return the file id this file version belongs to. + */ + public String getFileID() { + return this.fileID; + } + /** * Gets the version ID of this version of the file. * @return the version ID of this version of the file. diff --git a/src/main/java/com/box/sdk/BoxFileVersionLegalHold.java b/src/main/java/com/box/sdk/BoxFileVersionLegalHold.java new file mode 100644 index 000000000..4afd48cd3 --- /dev/null +++ b/src/main/java/com/box/sdk/BoxFileVersionLegalHold.java @@ -0,0 +1,185 @@ +package com.box.sdk; + +import java.net.URL; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import com.eclipsesource.json.JsonArray; +import com.eclipsesource.json.JsonObject; +import com.eclipsesource.json.JsonValue; + +/** + * Representing all holds on a file version. + * Note that every file version can have a maximum of one file version legal hold. + */ +@BoxResourceType("file_version_legal_hold") +public class BoxFileVersionLegalHold extends BoxResource { + + /** + * The URL template used for operation with file version legal hold with given ID. + * @see #getInfo(String...) + */ + private static final URLTemplate FILE_VERSION_HOLD_URL_TEMPLATE = new URLTemplate("file_version_legal_holds/%s"); + + /** + * Constructs a file version legal hold with a given ID. + * + * @param api the API connection to be used by the resource. + * @param id the ID of the resource. + */ + public BoxFileVersionLegalHold(BoxAPIConnection api, String id) { + super(api, id); + } + + /** + * @param fields the fields to retrieve. + * @return information about this file version legal hold. + */ + public BoxFileVersionLegalHold.Info getInfo(String ... fields) { + QueryStringBuilder builder = new QueryStringBuilder(); + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + URL url = FILE_VERSION_HOLD_URL_TEMPLATE.buildWithQuery( + this.getAPI().getBaseURL(), builder.toString(), this.getID()); + BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); + return new Info(responseJSON); + } + + /** + * Contains information about the file version legal hold. + */ + public class Info extends BoxResource.Info { + + /** + * Used for file version in case it was retrieved separately from file. + */ + private static final String DEFAULT_FILE_ID = "0"; + + /** + * @see #getFileVersion() + */ + private BoxFileVersion fileVersion; + + /** + * @see #getFile() + */ + private BoxFile.Info file; + + /** + * @see #getAssignments() + */ + private List assignments; + + /** + * @see #getDeletedAt() + */ + private Date deletedAt; + + /** + * Constructs an empty Info object. + */ + public Info() { + super(); + } + + /** + * Constructs an Info object by parsing information from a JSON string. + * @param json the JSON string to parse. + */ + public Info(String json) { + super(json); + } + + /** + * Constructs an Info object using an already parsed JSON object. + * @param jsonObject the parsed JSON object. + */ + Info(JsonObject jsonObject) { + super(jsonObject); + } + + /** + * {@inheritDoc} + */ + @Override + public BoxResource getResource() { + return BoxFileVersionLegalHold.this; + } + + /** + * @return the file version that is held. + */ + public BoxFileVersion getFileVersion() { + return this.fileVersion; + } + + /** + * @return the parent file of the file version that is held. + * Note that there is no guarantee that the current version of this file is held. + */ + public BoxFile.Info getFile() { + return this.file; + } + + /** + * @return iterable with the assignments contributing to this file version legal hold. + */ + public Iterable getAssignments() { + return this.assignments; + } + + /** + * @return time that this file version legal hold was deleted. + */ + public Date getDeletedAt() { + return this.deletedAt; + } + + /** + * {@inheritDoc} + */ + @Override + void parseJSONMember(JsonObject.Member member) { + super.parseJSONMember(member); + String memberName = member.getName(); + JsonValue value = member.getValue(); + try { + if (memberName.equals("file")) { + JsonObject fileJSON = value.asObject(); + if (this.file == null) { + String fileID = fileJSON.get("id").asString(); + BoxFile file = new BoxFile(getAPI(), fileID); + this.file = file.new Info(fileJSON); + } else { + this.file.update(fileJSON); + } + if (this.fileVersion != null) { + this.fileVersion.setFileID(this.file.getID()); + } + } else if (memberName.equals("file_version")) { + JsonObject versionJSON = value.asObject(); + String fileID = this.file != null ? this.file.getID() : DEFAULT_FILE_ID; + this.fileVersion = new BoxFileVersion(getAPI(), versionJSON, fileID); + } else if (memberName.equals("legal_hold_policy_assignments")) { + JsonArray array = value.asArray(); + this.assignments = new ArrayList(); + for (JsonValue assignmentJSON : array) { + String assignmentID = ((JsonObject) assignmentJSON).get("id").asString(); + BoxLegalHoldAssignment assignment = new BoxLegalHoldAssignment(getAPI(), assignmentID); + this.assignments.add(assignment.new Info((JsonObject) assignmentJSON)); + } + } else if (memberName.equals("deleted_at")) { + this.deletedAt = BoxDateFormat.parse(value.asString()); + } + } catch (ParseException e) { + assert false : "A ParseException indicates a bug in the SDK."; + } + } + + } +} diff --git a/src/main/java/com/box/sdk/BoxLegalHold.java b/src/main/java/com/box/sdk/BoxLegalHold.java index a4f0659ba..65fc2b74f 100644 --- a/src/main/java/com/box/sdk/BoxLegalHold.java +++ b/src/main/java/com/box/sdk/BoxLegalHold.java @@ -22,6 +22,10 @@ public class BoxLegalHold extends BoxResource { private static final URLTemplate LEGAL_HOLD_URL_TEMPLATE = new URLTemplate("legal_hold_policies/%s"); private static final URLTemplate ALL_LEGAL_HOLD_URL_TEMPLATE = new URLTemplate("legal_hold_policies"); + private static final URLTemplate LEGAL_HOLD_ASSIGNMENTS_URL_TEMPLATE + = new URLTemplate("legal_hold_policies/%s/assignments"); + private static final URLTemplate LIST_OF_FILE_VERSION_HOLDS_URL_TEMPLATE + = new URLTemplate("file_version_legal_holds"); private static final int DEFAULT_LIMIT = 100; /** @@ -153,6 +157,93 @@ protected BoxLegalHold.Info factory(JsonObject jsonObject) { }; } + /** + * Assigns this legal holds policy to the given box resource. + * Currently only {@link BoxFile}, {@link BoxFileVersion}, {@link BoxFolder} and {@link BoxUser} are supported. + * @param resource the box resource to assign legal hold policy to. + * @return info about created legal hold policy assignment. + */ + public BoxLegalHoldAssignment.Info assignTo(BoxResource resource) { + return BoxLegalHoldAssignment.create( + this.getAPI(), this.getID(), BoxResource.getResourceType(resource.getClass()), resource.getID()); + } + + /** + * Returns iterable containing assignments for this single legal hold policy. + * @param fields the fields to retrieve. + * @return an iterable containing assignments for this single legal hold policy. + */ + public Iterable getAssignments(String ... fields) { + return this.getAssignments(null, null, DEFAULT_LIMIT, fields); + } + + /** + * Returns iterable containing assignments for this single legal hold policy. + * Parameters can be used to filter retrieved assignments. + * @param type filter assignments of this type only. + * Can be "file_version", "file", "folder", "user" or null if no type filter is necessary. + * @param id filter assignments to this ID only. Can be null if no id filter is necessary. + * @param limit the limit of entries per page. Default limit is 100. + * @param fields the fields to retrieve. + * @return an iterable containing assignments for this single legal hold policy. + */ + public Iterable getAssignments(String type, String id, int limit, String ... fields) { + QueryStringBuilder builder = new QueryStringBuilder(); + if (type != null) { + builder.appendParam("assign_to_type", type); + } + if (id != null) { + builder.appendParam("assign_to_id", id); + } + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + return new BoxResourceIterable( + this.getAPI(), LEGAL_HOLD_ASSIGNMENTS_URL_TEMPLATE.buildWithQuery( + this.getAPI().getBaseURL(), builder.toString(), this.getID()), limit) { + + @Override + protected BoxLegalHoldAssignment.Info factory(JsonObject jsonObject) { + BoxLegalHoldAssignment assignment = new BoxLegalHoldAssignment( + BoxLegalHold.this.getAPI(), jsonObject.get("id").asString()); + return assignment.new Info(jsonObject); + } + }; + } + + /** + * Returns iterable with all non-deleted file version legal holds for this legal hold policy. + * @param fields the fields to retrieve. + * @return an iterable containing file version legal holds info. + */ + public Iterable getFileVersionHolds(String ... fields) { + return this.getFileVersionHolds(DEFAULT_LIMIT, fields); + } + + /** + * Returns iterable with all non-deleted file version legal holds for this legal hold policy. + * @param limit the limit of entries per response. The default value is 100. + * @param fields the fields to retrieve. + * @return an iterable containing file version legal holds info. + */ + public Iterable getFileVersionHolds(int limit, String ... fields) { + QueryStringBuilder queryString = new QueryStringBuilder().appendParam("policy_id", this.getID()); + if (fields.length > 0) { + queryString.appendParam("fields", fields); + } + URL url = LIST_OF_FILE_VERSION_HOLDS_URL_TEMPLATE.buildWithQuery(getAPI().getBaseURL(), queryString.toString()); + return new BoxResourceIterable(getAPI(), url, limit) { + + @Override + protected BoxFileVersionLegalHold.Info factory(JsonObject jsonObject) { + BoxFileVersionLegalHold assignment + = new BoxFileVersionLegalHold(getAPI(), jsonObject.get("id").asString()); + return assignment.new Info(jsonObject); + } + + }; + } + /** * Contains information about the legal hold policy. */ diff --git a/src/main/java/com/box/sdk/BoxLegalHoldAssignment.java b/src/main/java/com/box/sdk/BoxLegalHoldAssignment.java new file mode 100644 index 000000000..4d442ef2f --- /dev/null +++ b/src/main/java/com/box/sdk/BoxLegalHoldAssignment.java @@ -0,0 +1,265 @@ +package com.box.sdk; + +import java.net.URL; +import java.text.ParseException; +import java.util.Date; + +import com.eclipsesource.json.JsonObject; +import com.eclipsesource.json.JsonValue; + +/** + * Represents a legal hold policy assignment. + * Legal hold assignments are used to assign legal hold policies to custodians, folders, files, or file versions. + * + * @see Box legal holds + * + *

Unless otherwise noted, the methods in this class can throw an unchecked {@link BoxAPIException} (unchecked + * meaning that the compiler won't force you to handle it) if an error occurs. If you wish to implement custom error + * handling for errors related to the Box REST API, you should capture this exception explicitly.

+ */ +@BoxResourceType("legal_hold_assignment") +public class BoxLegalHoldAssignment extends BoxResource { + + /** + * Used to assign legal hold policy to file version. + */ + public static final String TYPE_FILE_VERSION = BoxFileVersion.getResourceType(BoxFileVersion.class); + + /** + * Used to assign legal hold policy to file. + */ + public static final String TYPE_FILE = BoxFile.getResourceType(BoxFile.class); + + /** + * Used to assign legal hold policy to folder. + */ + public static final String TYPE_FOLDER = BoxFolder.getResourceType(BoxFolder.class); + + /** + * Used to assign legal hold policy to user. + */ + public static final String TYPE_USER = BoxUser.getResourceType(BoxUser.class); + + /** + * The URL template used for operation with legal hold policy assignments. + */ + private static final URLTemplate ASSIGNMENTS_URL_TEMPLATE = new URLTemplate("legal_hold_policy_assignments"); + + /** + * The URL template used for operation with legal hold policy assignment with given ID. + */ + private static final URLTemplate LEGAL_HOLD_ASSIGNMENT_URL_TEMPLATE + = new URLTemplate("legal_hold_policy_assignments/%s"); + + /** + * Constructs a BoxLegalHoldAssignment for a resource with a given ID. + * + * @param api the API connection to be used by the resource. + * @param id the ID of the resource. + */ + public BoxLegalHoldAssignment(BoxAPIConnection api, String id) { + super(api, id); + } + + /** + * Creates new legal hold policy assignment. + * @param api the API connection to be used by the resource. + * @param policyID ID of policy to create assignment for. + * @param resourceType type of target resource. Can be 'file_version', 'file', 'folder', or 'user'. + * @param resourceID ID of the target resource. + * @return info about created legal hold policy assignment. + */ + public static BoxLegalHoldAssignment.Info create(BoxAPIConnection api, + String policyID, String resourceType, String resourceID) { + URL url = ASSIGNMENTS_URL_TEMPLATE.build(api.getBaseURL()); + BoxJSONRequest request = new BoxJSONRequest(api, url, "POST"); + + JsonObject requestJSON = new JsonObject() + .add("policy_id", policyID) + .add("assign_to", new JsonObject() + .add("type", resourceType) + .add("id", resourceID)); + request.setBody(requestJSON.toString()); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); + BoxLegalHoldAssignment createdAssignment = new BoxLegalHoldAssignment(api, responseJSON.get("id").asString()); + return createdAssignment.new Info(responseJSON); + } + + /** + * Deletes the legal hold policy assignment. + */ + public void delete() { + URL url = LEGAL_HOLD_ASSIGNMENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); + BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE"); + BoxAPIResponse response = request.send(); + response.disconnect(); + } + + /** + * @param fields the fields to retrieve. + * @return information about this retention policy. + */ + public BoxLegalHoldAssignment.Info getInfo(String ... fields) { + QueryStringBuilder builder = new QueryStringBuilder(); + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + URL url = LEGAL_HOLD_ASSIGNMENT_URL_TEMPLATE.buildWithQuery( + this.getAPI().getBaseURL(), builder.toString(), this.getID()); + BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); + return new Info(responseJSON); + } + + /** + * Contains information about the legal hold policy. + */ + public class Info extends BoxResource.Info { + + /** + * @see #getLegalHold() + */ + private BoxLegalHold.Info legalHold; + + /** + * @see #getAssignedBy() + */ + private BoxUser.Info assignedBy; + + /** + * @see #getAssignedAt() + */ + private Date assignedAt; + + /** + * @see #getDeletedAt() + */ + private Date deletedAt; + + /** + * @see #getAssignedToType() + */ + private String assignedToType; + + /** + * @see #getAssignedToID() + */ + private String assignedToID; + + /** + * Constructs an empty Info object. + */ + public Info() { + super(); + } + + /** + * Constructs an Info object by parsing information from a JSON string. + * @param json the JSON string to parse. + */ + public Info(String json) { + super(json); + } + + /** + * Constructs an Info object using an already parsed JSON object. + * @param jsonObject the parsed JSON object. + */ + Info(JsonObject jsonObject) { + super(jsonObject); + } + + /** + * {@inheritDoc} + */ + @Override + public BoxResource getResource() { + return BoxLegalHoldAssignment.this; + } + + /** + * @return info about the policy that this legal hold policy assignment is part of. + */ + public BoxLegalHold.Info getLegalHold() { + return this.legalHold; + } + + /** + * @return the info about the user who created that legal hold policy assignment. + */ + public BoxUser.Info getAssignedBy() { + return this.assignedBy; + } + + /** + * @return the time that the legal hold policy assignment was created. + */ + public Date getAssignedAt() { + return this.assignedAt; + } + + /** + * @return the time that the assignment release request was sent. + */ + public Date getDeletedAt() { + return this.deletedAt; + } + + /** + * @return the entity type that this is assigned to. + */ + public String getAssignedToType() { + return this.assignedToType; + } + + /** + * @return the entity id that this is assigned to. + */ + public String getAssignedToID() { + return this.assignedToID; + } + + /** + * {@inheritDoc} + */ + @Override + void parseJSONMember(JsonObject.Member member) { + super.parseJSONMember(member); + String memberName = member.getName(); + JsonValue value = member.getValue(); + try { + if (memberName.equals("legal_hold_policy")) { + JsonObject policyJSON = value.asObject(); + if (this.legalHold == null) { + String policyID = policyJSON.get("id").asString(); + BoxLegalHold policy = new BoxLegalHold(getAPI(), policyID); + this.legalHold = policy.new Info(policyJSON); + } else { + this.legalHold.update(policyJSON); + } + } else if (memberName.equals("assigned_to")) { + JsonObject assignmentJSON = value.asObject(); + this.assignedToType = assignmentJSON.get("type").asString(); + this.assignedToID = assignmentJSON.get("id").asString(); + } else if (memberName.equals("assigned_by")) { + JsonObject userJSON = value.asObject(); + if (this.assignedBy == null) { + String userID = userJSON.get("id").asString(); + BoxUser user = new BoxUser(getAPI(), userID); + this.assignedBy = user.new Info(userJSON); + } else { + this.assignedBy.update(userJSON); + } + } else if (memberName.equals("assigned_at")) { + this.assignedAt = BoxDateFormat.parse(value.asString()); + } else if (memberName.equals("deleted_at")) { + this.deletedAt = BoxDateFormat.parse(value.asString()); + } + } catch (ParseException e) { + assert false : "A ParseException indicates a bug in the SDK."; + } + } + } +} diff --git a/src/main/java/com/box/sdk/BoxResource.java b/src/main/java/com/box/sdk/BoxResource.java index 10f64068b..15931ed51 100644 --- a/src/main/java/com/box/sdk/BoxResource.java +++ b/src/main/java/com/box/sdk/BoxResource.java @@ -58,6 +58,8 @@ private static Map> initResourceClassByType result.put(getResourceType(BoxRetentionPolicyAssignment.class), BoxRetentionPolicyAssignment.class); result.put(getResourceType(BoxFileVersionRetention.class), BoxFileVersionRetention.class); result.put(getResourceType(BoxLegalHold.class), BoxLegalHold.class); + result.put(getResourceType(BoxLegalHoldAssignment.class), BoxLegalHoldAssignment.class); + result.put(getResourceType(BoxFileVersionLegalHold.class), BoxFileVersionLegalHold.class); return Collections.unmodifiableMap(result); } diff --git a/src/test/java/com/box/sdk/BoxFileVersionLegalHoldTest.java b/src/test/java/com/box/sdk/BoxFileVersionLegalHoldTest.java new file mode 100644 index 000000000..f277217e3 --- /dev/null +++ b/src/test/java/com/box/sdk/BoxFileVersionLegalHoldTest.java @@ -0,0 +1,97 @@ +package com.box.sdk; + +import java.util.Date; +import java.util.Iterator; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import com.eclipsesource.json.JsonObject; + +/** + * {@link BoxFileVersionLegalHoldTest} related unit tests. + */ +public class BoxFileVersionLegalHoldTest { + + /** + * Unit test for {@link BoxFileVersionLegalHold#getInfo(String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetInfoSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/file_version_legal_holds/0?fields=file", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + BoxFileVersionLegalHold hold = new BoxFileVersionLegalHold(api, "0"); + hold.getInfo("file"); + } + + /** + * Unit test for {@link BoxFileVersionLegalHold#getInfo(String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetInfoParseAllFieldsCorrectly() { + final String id = "240997"; + final String fileVersionID = "141649417"; + final String fileID = "5025122933"; + final String fileEtag = "1"; + final String firstPolicyID = "255473"; + final String secondPolicyID = "255617"; + final Date deletedAt = null; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"legal_hold\",\n" + + " \"id\": \"240997\",\n" + + " \"file_version\": {\n" + + " \"type\": \"file_version\",\n" + + " \"id\": \"141649417\"\n" + + " },\n" + + " \"file\": {\n" + + " \"type\": \"file\",\n" + + " \"id\": \"5025122933\",\n" + + " \"etag\": \"1\"\n" + + " },\n" + + " \"legal_hold_policy_assignments\": [\n" + + " {\n" + + " \"type\": \"legal_hold_policy_assignment\",\n" + + " \"id\": \"255473\"\n" + + " },\n" + + " {\n" + + " \"type\": \"legal_hold_policy_assignment\",\n" + + " \"id\": \"255617\"\n" + + " }\n" + + " ],\n" + + " \"deleted_at\": null\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxFileVersionLegalHold hold = new BoxFileVersionLegalHold(api, id); + BoxFileVersionLegalHold.Info info = hold.getInfo(); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(fileVersionID, info.getFileVersion().getID()); + Assert.assertEquals(fileID, info.getFileVersion().getFileID()); + Assert.assertEquals(fileID, info.getFile().getID()); + Assert.assertEquals(fileEtag, info.getFile().getEtag()); + Assert.assertEquals(deletedAt, info.getDeletedAt()); + Iterator iterator = info.getAssignments().iterator(); + Assert.assertEquals(firstPolicyID, iterator.next().getID()); + Assert.assertEquals(secondPolicyID, iterator.next().getID()); + Assert.assertEquals(false, iterator.hasNext()); + } +} diff --git a/src/test/java/com/box/sdk/BoxLegalHoldAssignmentTest.java b/src/test/java/com/box/sdk/BoxLegalHoldAssignmentTest.java new file mode 100644 index 000000000..fd1f5367a --- /dev/null +++ b/src/test/java/com/box/sdk/BoxLegalHoldAssignmentTest.java @@ -0,0 +1,208 @@ +package com.box.sdk; + +import java.text.ParseException; +import java.util.Date; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import com.eclipsesource.json.JsonObject; + +/** + * {@link BoxLegalHoldAssignment} related unit tests. + */ +public class BoxLegalHoldAssignmentTest { + + /** + * Unit test for {@link BoxLegalHoldAssignment#getInfo(String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetInfoSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/legal_hold_policy_assignments/0?fields=assigned_by", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + BoxLegalHoldAssignment assignment = new BoxLegalHoldAssignment(api, "0"); + assignment.getInfo("assigned_by"); + } + + /** + * Unit test for {@link BoxLegalHoldAssignment#getInfo(String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetInfoParseAllFieldsCorrectly() throws ParseException { + final String id = "255473"; + final String policyID = "166757"; + final String policyName = "Bug Bash 5-12 Policy 3 updated"; + final String assignedToType = "user"; + final String assignedToID = "2030388321"; + final String assignedByID = "2030388322"; + final String assignedByName = "Steve Boxuser"; + final String assignedByLogin = "sboxuser@box.com"; + final Date assignedAt = BoxDateFormat.parse("2016-05-18T10:32:19-07:00"); + final Date deletedAt = null; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"legal_hold_policy_assignment\",\n" + + " \"id\": \"255473\",\n" + + " \"legal_hold_policy\": {\n" + + " \"type\": \"legal_hold_policy\",\n" + + " \"id\": \"166757\",\n" + + " \"policy_name\": \"Bug Bash 5-12 Policy 3 updated\"\n" + + " },\n" + + " \"assigned_to\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"2030388321\"\n" + + " },\n" + + " \"assigned_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"2030388322\",\n" + + " \"name\": \"Steve Boxuser\",\n" + + " \"login\": \"sboxuser@box.com\"\n" + + " },\n" + + " \"assigned_at\": \"2016-05-18T10:32:19-07:00\",\n" + + " \"deleted_at\": null\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxLegalHoldAssignment assignment = new BoxLegalHoldAssignment(api, id); + BoxLegalHoldAssignment.Info info = assignment.getInfo(); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(policyID, info.getLegalHold().getID()); + Assert.assertEquals(policyName, info.getLegalHold().getPolicyName()); + Assert.assertEquals(assignedToType, info.getAssignedToType()); + Assert.assertEquals(assignedToID, info.getAssignedToID()); + Assert.assertEquals(assignedByID, info.getAssignedBy().getID()); + Assert.assertEquals(assignedByName, info.getAssignedBy().getName()); + Assert.assertEquals(assignedByLogin, info.getAssignedBy().getLogin()); + Assert.assertEquals(assignedAt, info.getAssignedAt()); + Assert.assertEquals(deletedAt, info.getDeletedAt()); + } + + /** + * Unit test for {@link BoxLegalHoldAssignment#create(BoxAPIConnection, String, String, String)} + */ + @Test + @Category(UnitTest.class) + public void testCreateSendsCorrectJSON() { + final String policyID = "0"; + final String resourceType = BoxLegalHoldAssignment.TYPE_FILE_VERSION; + final String resourceID = "1"; + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals("https://api.box.com/2.0/legal_hold_policy_assignments", + request.getUrl().toString()); + Assert.assertEquals(policyID, json.get("policy_id").asString()); + Assert.assertEquals(resourceType, json.get("assign_to").asObject().get("type").asString()); + Assert.assertEquals(resourceID, json.get("assign_to").asObject().get("id").asString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + BoxLegalHoldAssignment.create(api, policyID, resourceType, resourceID); + } + + /** + * Unit test for {@link BoxLegalHoldAssignment#create(BoxAPIConnection, String, String, String)} + */ + @Test + @Category(UnitTest.class) + public void testCreateParseAllFieldsCorrectly() throws ParseException { + final String id = "255613"; + final String policyID = "166757"; + final String policyName = "Bug Bash 5-12 Policy 3 updated"; + final String assignedToType = BoxLegalHoldAssignment.TYPE_FILE; + final String assignedToID = "5025127885"; + final String assignedByID = "2030388321"; + final String assignedByName = "Steve Boxuser"; + final String assignedByLogin = "sboxuser@box.com"; + final Date assignedAt = BoxDateFormat.parse("2016-05-18T17:38:03-07:00"); + final Date deletedAt = null; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"legal_hold_policy_assignment\",\n" + + " \"id\": \"255613\",\n" + + " \"legal_hold_policy\": {\n" + + " \"type\": \"legal_hold_policy\",\n" + + " \"id\": \"166757\",\n" + + " \"policy_name\": \"Bug Bash 5-12 Policy 3 updated\"\n" + + " },\n" + + " \"assigned_to\": {\n" + + " \"type\": \"file\",\n" + + " \"id\": \"5025127885\"\n" + + " },\n" + + " \"assigned_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"2030388321\",\n" + + " \"name\": \"Steve Boxuser\",\n" + + " \"login\": \"sboxuser@box.com\"\n" + + " },\n" + + " \"assigned_at\": \"2016-05-18T17:38:03-07:00\",\n" + + " \"deleted_at\": null\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxLegalHoldAssignment.Info info = BoxLegalHoldAssignment.create(api, policyID, assignedToType, assignedToID); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(policyID, info.getLegalHold().getID()); + Assert.assertEquals(policyName, info.getLegalHold().getPolicyName()); + Assert.assertEquals(assignedToType, info.getAssignedToType()); + Assert.assertEquals(assignedToID, info.getAssignedToID()); + Assert.assertEquals(assignedByID, info.getAssignedBy().getID()); + Assert.assertEquals(assignedByName, info.getAssignedBy().getName()); + Assert.assertEquals(assignedByLogin, info.getAssignedBy().getLogin()); + Assert.assertEquals(assignedAt, info.getAssignedAt()); + Assert.assertEquals(deletedAt, info.getDeletedAt()); + } + + /** + * Unit test for {@link BoxLegalHoldAssignment#delete()} + */ + @Test + @Category(UnitTest.class) + public void testDeleteSendsCorrectJSON() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/legal_hold_policy_assignments/0", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + BoxLegalHoldAssignment assignment = new BoxLegalHoldAssignment(api, "0"); + assignment.delete(); + } +} diff --git a/src/test/java/com/box/sdk/BoxLegalHoldTest.java b/src/test/java/com/box/sdk/BoxLegalHoldTest.java index c05bec1ef..963708575 100644 --- a/src/test/java/com/box/sdk/BoxLegalHoldTest.java +++ b/src/test/java/com/box/sdk/BoxLegalHoldTest.java @@ -418,4 +418,204 @@ public void testGetAllParseAllFieldsCorrectly() { Assert.assertEquals(false, iterator.hasNext()); } + /** + * Unit test for {@link BoxLegalHold#assignTo(BoxResource)} + */ + @Test + @Category(UnitTest.class) + public void testAssignToSendsCorrectJSON() { + final String policyID = "0"; + final String resourceType = BoxLegalHoldAssignment.TYPE_FILE_VERSION; + final String resourceID = "1"; + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals("https://api.box.com/2.0/legal_hold_policy_assignments", + request.getUrl().toString()); + Assert.assertEquals(policyID, json.get("policy_id").asString()); + Assert.assertEquals(resourceType, json.get("assign_to").asObject().get("type").asString()); + Assert.assertEquals(resourceID, json.get("assign_to").asObject().get("id").asString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + BoxLegalHold policy = new BoxLegalHold(api, "0"); + BoxFileVersion version = new BoxFileVersion(api, "{\"id\": \"1\"}", "2"); + policy.assignTo(version); + } + + /** + * Unit test for {@link BoxLegalHold#getAssignments(String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetAssignmentsSendsCorrectRequestWithFields() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals( + "https://api.box.com/2.0/legal_hold_policies/0/assignments?fields=assigned_at&limit=100", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"entries\": []}"; + } + }; + } + }); + + BoxLegalHold policy = new BoxLegalHold(api, "0"); + Iterator iterator = policy.getAssignments("assigned_at").iterator(); + iterator.hasNext(); + } + + /** + * Unit test for {@link BoxLegalHold#getAssignments(String, String, int, String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetAssignmentsSendsCorrectRequestWithOptionalParams() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals( + "https://api.box.com/2.0/legal_hold_policies/0/assignments" + + "?assign_to_type=folder&assign_to_id=1&limit=99", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"entries\": []}"; + } + }; + } + }); + + BoxLegalHold policy = new BoxLegalHold(api, "0"); + Iterator iterator + = policy.getAssignments(BoxResource.getResourceType(BoxFolder.class), "1", 99).iterator(); + iterator.hasNext(); + } + + /** + * Unit test for {@link BoxLegalHold#getAssignments(String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetAssignmentsParseAllFieldsCorrectly() { + final String firstEntryID = "255473"; + final String secondEntryID = "123432"; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"entries\": [\n" + + " {\n" + + " \"type\": \"legal_hold_policy_assignment\",\n" + + " \"id\": \"255473\"\n" + + " },\n" + + " {\n" + + " \"type\": \"legal_hold_policy_assignment\",\n" + + " \"id\": \"123432\"\n" + + " }\n" + + " ],\n" + + " \"limit\": 100,\n" + + " \"order\": [\n" + + " {\n" + + " \"by\": \"retention_policy_id, retention_policy_object_id\",\n" + + " \"direction\": \"ASC\"\n" + + " }\n" + + " ]\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxLegalHold policy = new BoxLegalHold(api, "0"); + Iterator iterator = policy.getAssignments().iterator(); + BoxLegalHoldAssignment.Info info = iterator.next(); + Assert.assertEquals(firstEntryID, info.getID()); + info = iterator.next(); + Assert.assertEquals(secondEntryID, info.getID()); + Assert.assertEquals(false, iterator.hasNext()); + } + + /** + * Unit test for {@link BoxLegalHold#getFileVersionHolds(String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetFileVersionHoldsSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/file_version_legal_holds?policy_id=0&limit=100", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"entries\": []}"; + } + }; + } + }); + + BoxLegalHold policy = new BoxLegalHold(api, "0"); + Iterator iterator = policy.getFileVersionHolds().iterator(); + iterator.hasNext(); + } + + /** + * Unit test for {@link BoxLegalHold#getFileVersionHolds(String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetFileVersionHoldsParseAllFieldsCorrectly() { + final String firstID = "241001"; + final String secondID = "241005"; + final String thirdID = "241009"; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"entries\": [\n" + + " {\n" + + " \"type\": \"legal_hold\",\n" + + " \"id\": \"241001\"\n" + + " },\n" + + " {\n" + + " \"type\": \"legal_hold\",\n" + + " \"id\": \"241005\"\n" + + " },\n" + + " {\n" + + " \"type\": \"legal_hold\",\n" + + " \"id\": \"241009\"\n" + + " }\n" + + " ],\n" + + " \"limit\": 100,\n" + + " \"order\": [\n" + + " {\n" + + " \"by\": \"retention_policy_set_id, file_version_id\",\n" + + " \"direction\": \"ASC\"\n" + + " }\n" + + " ]\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxLegalHold policy = new BoxLegalHold(api, "0"); + Iterator iterator = policy.getFileVersionHolds().iterator(); + Assert.assertEquals(firstID, iterator.next().getID()); + Assert.assertEquals(secondID, iterator.next().getID()); + Assert.assertEquals(thirdID, iterator.next().getID()); + Assert.assertEquals(false, iterator.hasNext()); + } } From 059cd28a5124b8127e00f6697fa7f5e8c8f6df98 Mon Sep 17 00:00:00 2001 From: gleb-urvanov Date: Wed, 2 Nov 2016 16:07:04 +0100 Subject: [PATCH 023/119] invite user feature documented --- doc/users.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/doc/users.md b/doc/users.md index f82fd8a6d..4cceb7b6f 100644 --- a/doc/users.md +++ b/doc/users.md @@ -7,6 +7,7 @@ Users represent an individual's account on Box. * [Create An Enterprise User](#create-an-enterprise-user) * [Update User](#update-user) * [Delete User](#delete-user) +* [Invite User](#invite-user) * [Get Email Aliases](#get-email-aliases) * [Add Email Alias](#add-email-alias) * [Delete Email Alias](#delete-email-alias) @@ -66,6 +67,18 @@ user.delete(false, false); [delete]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxUser.html#delete(boolean,%20boolean) +Invite User +----------- + +To invite an existing user to join an Enterprise call the [`inviteUser(String, String)`][invite] method. + +```java +BoxUser user = new BoxUser(api, "0"); +user.invite("Enterprise ID", "Invited User Login"); +``` + +[invite]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxUser.html#inviteUser(java.lang.String,%java.lang.String) + Get Email Aliases ----------------- From b8747105f20e01f66606e3ebde20b41027e60546 Mon Sep 17 00:00:00 2001 From: gleb-urvanov Date: Fri, 18 Nov 2016 12:56:31 +0100 Subject: [PATCH 024/119] fixed and tests for delete and promote file version --- src/main/java/com/box/sdk/BoxFileVersion.java | 28 +++- .../java/com/box/sdk/BoxFileVersionTest.java | 120 ++++++++++++++++++ 2 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 src/test/java/com/box/sdk/BoxFileVersionTest.java diff --git a/src/main/java/com/box/sdk/BoxFileVersion.java b/src/main/java/com/box/sdk/BoxFileVersion.java index 27d3832b6..5e198e075 100644 --- a/src/main/java/com/box/sdk/BoxFileVersion.java +++ b/src/main/java/com/box/sdk/BoxFileVersion.java @@ -19,7 +19,7 @@ public class BoxFileVersion extends BoxResource { private static final URLTemplate VERSION_URL_TEMPLATE = new URLTemplate("files/%s/versions/%s"); private static final int BUFFER_SIZE = 8192; - private final String fileID; + private String fileID; private String versionID; private String sha1; @@ -44,6 +44,14 @@ public BoxFileVersion(BoxAPIConnection api, String json, String fileID) { super(api, jsonObject.get("id").asString()); this.fileID = fileID; + this.parseJSON(jsonObject); + } + + /** + * Method used to update fields with values received from API. + * @param jsonObject JSON-encoded info about File Version object. + */ + private void parseJSON(JsonObject jsonObject) { for (JsonObject.Member member : jsonObject) { JsonValue value = member.getValue(); if (value.isNull()) { @@ -78,6 +86,21 @@ public BoxFileVersion(BoxAPIConnection api, String json, String fileID) { } } + /** + * Used if no or wrong file id was set with constructor. + * @param fileID the file id this file version belongs to. + */ + public void setFileID(String fileID) { + this.fileID = fileID; + } + + /** + * @return the file id this file version belongs to. + */ + public String getFileID() { + return this.fileID; + } + /** * Gets the version ID of this version of the file. * @return the version ID of this version of the file. @@ -200,7 +223,8 @@ public void promote() { BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); request.setBody(jsonObject.toString()); - BoxAPIResponse response = request.send(); + BoxJSONResponse response = (BoxJSONResponse) request.send(); response.disconnect(); + this.parseJSON(JsonObject.readFrom(response.getJSON())); } } diff --git a/src/test/java/com/box/sdk/BoxFileVersionTest.java b/src/test/java/com/box/sdk/BoxFileVersionTest.java new file mode 100644 index 000000000..c7bbcbc66 --- /dev/null +++ b/src/test/java/com/box/sdk/BoxFileVersionTest.java @@ -0,0 +1,120 @@ +package com.box.sdk; + +import java.text.ParseException; +import java.util.Date; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import com.eclipsesource.json.JsonObject; + +/** + * {@link BoxFileVersion} related tests. + */ +public class BoxFileVersionTest { + + /** + * Unit test for {@link BoxFileVersion#delete()} + */ + @Test + @Category(UnitTest.class) + public void testDeleteSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/files/0/versions/1", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + BoxFile file = new BoxFile(api, "0"); + BoxFileVersion version = new BoxFileVersion(api, new JsonObject().add("id", "1"), file.getID()); + version.delete(); + } + + /** + * Unit test for {@link BoxFileVersion#promote()} + */ + @Test + @Category(UnitTest.class) + public void testPromoteSendsCorrectJSON() { + final String type = "file_version"; + final String id = "1"; + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals("https://api.box.com/2.0/files/0/versions/current", request.getUrl().toString()); + Assert.assertEquals(type, json.get("type").asString()); + Assert.assertEquals(id, json.get("id").asString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + BoxFileVersion version = new BoxFileVersion(api, new JsonObject().add("id", "1"), "0"); + version.promote(); + } + + /** + * Unit test for {@link BoxFileVersion#promote()} + */ + @Test + @Category(UnitTest.class) + public void testPromoteParseAllFieldsCorrectly() throws ParseException { + final String id = "871399"; + final String sha1 = "12039d6dd9a7e6eefc78846802e"; + final String name = "Stark Family Lineage.doc"; + final long size = 11; + final Date createdAt = BoxDateFormat.parse("2013-11-20T13:20:50-08:00"); + final Date modifiedAt = BoxDateFormat.parse("2013-11-20T13:26:48-08:00"); + final String modifiedById = "13711334"; + final String modifiedByName = "Eddard Stark"; + final String modifiedByLogin = "ned@winterfell.com"; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"file_version\",\n" + + " \"id\": \"871399\",\n" + + " \"sha1\": \"12039d6dd9a7e6eefc78846802e\",\n" + + " \"name\": \"Stark Family Lineage.doc\",\n" + + " \"size\": 11,\n" + + " \"created_at\": \"2013-11-20T13:20:50-08:00\",\n" + + " \"modified_at\": \"2013-11-20T13:26:48-08:00\",\n" + + " \"modified_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"13711334\",\n" + + " \"name\": \"Eddard Stark\",\n" + + " \"login\": \"ned@winterfell.com\"\n" + + " }\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxFileVersion version = new BoxFileVersion(api, new JsonObject().add("id", id), "0"); + version.promote(); + Assert.assertEquals(id, version.getID()); + Assert.assertEquals(sha1, version.getSha1()); + Assert.assertEquals(name, version.getName()); + Assert.assertEquals(size, version.getSize()); + Assert.assertEquals(createdAt, version.getCreatedAt()); + Assert.assertEquals(modifiedAt, version.getModifiedAt()); + Assert.assertEquals(modifiedById, version.getModifiedBy().getID()); + Assert.assertEquals(modifiedByName, version.getModifiedBy().getName()); + Assert.assertEquals(modifiedByLogin, version.getModifiedBy().getLogin()); + + } +} From 57cdfa20326e1c8b4ae73eb6957ae9e089d97a3f Mon Sep 17 00:00:00 2001 From: gleb-urvanov Date: Fri, 18 Nov 2016 20:31:06 +0100 Subject: [PATCH 025/119] unit tests for BoxUser methods --- src/test/java/com/box/sdk/BoxUserTest.java | 407 +++++++++++++++++++++ 1 file changed, 407 insertions(+) diff --git a/src/test/java/com/box/sdk/BoxUserTest.java b/src/test/java/com/box/sdk/BoxUserTest.java index 84cf83c1f..0a7129c28 100644 --- a/src/test/java/com/box/sdk/BoxUserTest.java +++ b/src/test/java/com/box/sdk/BoxUserTest.java @@ -17,6 +17,7 @@ import static org.junit.Assert.fail; import org.hamcrest.Matchers; +import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -416,6 +417,412 @@ public String getJSON() { user.addEmailAlias(email); } + /** + * Unit test for {@link BoxUser#getInfo(String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetInfoSendsCorrectRequestWithoutParams() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/users/0", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + BoxUser user = new BoxUser(api, "0"); + user.getInfo(); + } + + /** + * Unit test for {@link BoxUser#getInfo(String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetInfoSendsCorrectRequestWithFields() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/users/0?fields=name%2Cstatus", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + BoxUser user = new BoxUser(api, "0"); + user.getInfo("name", "status"); + } + + /** + * Unit test for {@link BoxUser#getInfo(String...)} + */ + @Test + @Category(UnitTest.class) + public void testGetInfoParseAllFieldsCorrectly() throws ParseException { + final String id = "10543463"; + final String name = "Arielle Frey"; + final String login = "ariellefrey@box.com"; + final Date createdAt = BoxDateFormat.parse("2011-01-07T12:37:09-08:00"); + final Date modifiedAt = BoxDateFormat.parse("2014-05-30T10:39:47-07:00"); + final String language = "en"; + final String timezone = "America/Los_Angeles"; + final long spaceAmount = 10737418240L; + final long spaceUsed = 558732L; + final long maxUploadSize = 5368709120L; + final BoxUser.Status status = BoxUser.Status.ACTIVE; + final String jobTitle = ""; + final String phone = ""; + final String address = ""; + final String avatarURL = "https://blosserdemoaccount.app.box.com/api/avatar/large/10543465"; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"user\",\n" + + " \"id\": \"10543463\",\n" + + " \"name\": \"Arielle Frey\",\n" + + " \"login\": \"ariellefrey@box.com\",\n" + + " \"created_at\": \"2011-01-07T12:37:09-08:00\",\n" + + " \"modified_at\": \"2014-05-30T10:39:47-07:00\",\n" + + " \"language\": \"en\",\n" + + " \"timezone\": \"America/Los_Angeles\",\n" + + " \"space_amount\": 10737418240,\n" + + " \"space_used\": 558732,\n" + + " \"max_upload_size\": 5368709120,\n" + + " \"status\": \"active\",\n" + + " \"job_title\": \"\",\n" + + " \"phone\": \"\",\n" + + " \"address\": \"\",\n" + + " \"avatar_url\": \"https://blosserdemoaccount.app.box.com/api/avatar/large/10543465\"\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxUser user = new BoxUser(api, id); + BoxUser.Info info = user.getInfo(); + Assert.assertEquals(id, info.getID()); + Assert.assertEquals(name, info.getName()); + Assert.assertEquals(login, info.getLogin()); + Assert.assertEquals(createdAt, info.getCreatedAt()); + Assert.assertEquals(modifiedAt, info.getModifiedAt()); + Assert.assertEquals(language, info.getLanguage()); + Assert.assertEquals(timezone, info.getTimezone()); + Assert.assertEquals(spaceAmount, info.getSpaceAmount()); + Assert.assertEquals(spaceUsed, info.getSpaceUsed()); + Assert.assertEquals(maxUploadSize, info.getMaxUploadSize()); + Assert.assertEquals(status, info.getStatus()); + Assert.assertEquals(jobTitle, info.getJobTitle()); + Assert.assertEquals(phone, info.getPhone()); + Assert.assertEquals(address, info.getAddress()); + Assert.assertEquals(avatarURL, info.getAvatarURL()); + + } + + /** + * Unit test for {@link BoxUser#getCurrentUser(BoxAPIConnection)} + */ + @Test + @Category(UnitTest.class) + public void testGetCurrentUserSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/users/me", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + BoxUser.getCurrentUser(api); + } + + /** + * Unit test for {@link BoxUser#getCurrentUser(BoxAPIConnection)} + */ + @Test + @Category(UnitTest.class) + public void testGetCurrentUserParseAllFieldsCorrectly() { + final String id = "17738362"; + + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"user\",\n" + + " \"id\": \"17738362\",\n" + + " \"name\": \"sean rose\",\n" + + " \"login\": \"sean@box.com\",\n" + + " \"created_at\": \"2012-03-26T15:43:07-07:00\",\n" + + " \"modified_at\": \"2012-12-12T11:34:29-08:00\",\n" + + " \"language\": \"en\",\n" + + " \"space_amount\": 5368709120,\n" + + " \"space_used\": 2377016,\n" + + " \"max_upload_size\": 262144000,\n" + + " \"status\": \"active\",\n" + + " \"job_title\": \"Employee\",\n" + + " \"phone\": \"5555555555\",\n" + + " \"address\": \"555 Office Drive\",\n" + + " \"avatar_url\": \"https://app.box.com/api/avatar/large/17738362\"\n" + + "}"); + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + + BoxUser user = BoxUser.getCurrentUser(api); + Assert.assertEquals(id, user.getID()); + + } + + /** + * Unit test for {@link BoxUser#delete(boolean, boolean)} + */ + @Test + @Category(UnitTest.class) + public void testDeleteUserSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/users/0?notify=true&force=true", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + BoxUser user = new BoxUser(api, "0"); + user.delete(true, true); + } + + /** + * Unit test for {@link BoxUser#deleteEmailAlias(String)} + */ + @Test + @Category(UnitTest.class) + public void testDeleteEmailAliasSendsCorrectRequest() { + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new RequestInterceptor() { + @Override + public BoxAPIResponse onRequest(BoxAPIRequest request) { + Assert.assertEquals("https://api.box.com/2.0/users/0/email_aliases/1", + request.getUrl().toString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + BoxUser user = new BoxUser(api, "0"); + user.deleteEmailAlias("1"); + } + + /** + * Unit test for {@link BoxUser#moveFolderToUser(String)} + */ + @Test + @Category(UnitTest.class) + public void testMoveFolderToUserSendsCorrectJson() { + final String ownedByID = "0"; + + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(new JSONRequestInterceptor() { + @Override + protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { + Assert.assertEquals("https://api.box.com/2.0/users/1/folders/0", + request.getUrl().toString()); + Assert.assertEquals(ownedByID, json.get("owned_by").asObject().get("id").asString()); + return new BoxJSONResponse() { + @Override + public String getJSON() { + return "{\"id\": \"0\"}"; + } + }; + } + }); + + BoxUser user = new BoxUser(api, "0"); + user.moveFolderToUser("1"); + + } + + /** + * Unit test for {@link BoxUser#moveFolderToUser(String)} + */ + @Test + @Category(UnitTest.class) + public void testMoveFolderToUserParseAllFieldsCorrectly() throws ParseException { + final String id = "11446498"; + final String sequenceID = "1"; + final String etag = "1"; + final String name = "Pictures"; + final Date createdAt = BoxDateFormat.parse("2012-12-12T10:53:43-08:00"); + final Date modifiedAt = BoxDateFormat.parse("2012-12-12T10:53:43-08:00"); + final String description = "Some pictures I took"; + final long size = 629644; + final String pathID = "0"; + final String pathSequenceID = null; + final String pathEtag = null; + final String pathName = "All Files"; + final String createdByID = "17738362"; + final String createdByName = "sean rose"; + final String createdByLogin = "sean@box.com"; + final String modifiedByID = "17738362"; + final String modifiedByName = "sean rose"; + final String modifiedByLogin = "sean@box.com"; + final String ownedByID = "17738362"; + final String ownedByName = "sean rose"; + final String ownedByLogin = "sean@box.com"; + final String url = "https://www.box.com/s/vspke7y05sb214wjokpk"; + final String downloadURL = null; + final String vanityUrl = null; + final boolean isPasswordEnabled = false; + final Date unsharedAt = null; + final long downloadCount = 0; + final long previewCount = 0; + final BoxSharedLink.Access access = BoxSharedLink.Access.OPEN; + final boolean canDownload = true; + final boolean canPreview = true; + final BoxUploadEmail.Access folderUploadEmailAccess = BoxUploadEmail.Access.OPEN; + final String folderUploadEmail = "upload.Picture.k13sdz1@u.box.com"; + final String parentID = "0"; + final String parentSequenceID = null; + final String parentEtag = null; + final String parentName = "All Files"; + final String itemStatus = "active"; + final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" + + " \"type\": \"folder\",\n" + + " \"id\": \"11446498\",\n" + + " \"sequence_id\": \"1\",\n" + + " \"etag\": \"1\",\n" + + " \"name\": \"Pictures\",\n" + + " \"created_at\": \"2012-12-12T10:53:43-08:00\",\n" + + " \"modified_at\": \"2012-12-12T10:53:43-08:00\",\n" + + " \"description\": \"Some pictures I took\",\n" + + " \"size\": 629644,\n" + + " \"path_collection\": {\n" + + " \"total_count\": 1,\n" + + " \"entries\": [\n" + + " {\n" + + " \"type\": \"folder\",\n" + + " \"id\": \"0\",\n" + + " \"sequence_id\": null,\n" + + " \"etag\": null,\n" + + " \"name\": \"All Files\"\n" + + " }\n" + + " ]\n" + + " },\n" + + " \"created_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"17738362\",\n" + + " \"name\": \"sean rose\",\n" + + " \"login\": \"sean@box.com\"\n" + + " },\n" + + " \"modified_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"17738362\",\n" + + " \"name\": \"sean rose\",\n" + + " \"login\": \"sean@box.com\"\n" + + " },\n" + + " \"owned_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"17738362\",\n" + + " \"name\": \"sean rose\",\n" + + " \"login\": \"sean@box.com\"\n" + + " },\n" + + " \"shared_link\": {\n" + + " \"url\": \"https://www.box.com/s/vspke7y05sb214wjokpk\",\n" + + " \"download_url\": null,\n" + + " \"vanity_url\": null,\n" + + " \"is_password_enabled\": false,\n" + + " \"unshared_at\": null,\n" + + " \"download_count\": 0,\n" + + " \"preview_count\": 0,\n" + + " \"access\": \"open\",\n" + + " \"permissions\": {\n" + + " \"can_download\": true,\n" + + " \"can_preview\": true\n" + + " }\n" + + " },\n" + + " \"folder_upload_email\": {\n" + + " \"access\": \"open\",\n" + + " \"email\": \"upload.Picture.k13sdz1@u.box.com\"\n" + + " },\n" + + " \"parent\": {\n" + + " \"type\": \"folder\",\n" + + " \"id\": \"0\",\n" + + " \"sequence_id\": null,\n" + + " \"etag\": null,\n" + + " \"name\": \"All Files\"\n" + + " },\n" + + " \"item_status\": \"active\"\n" + + "}"); + BoxAPIConnection api = new BoxAPIConnection(""); + api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); + BoxUser user = new BoxUser(api, id); + BoxFolder.Info folder = user.moveFolderToUser("0"); + Assert.assertEquals(id, folder.getID()); + Assert.assertEquals(sequenceID, folder.getSequenceID()); + Assert.assertEquals(etag, folder.getEtag()); + Assert.assertEquals(name, folder.getName()); + Assert.assertEquals(createdAt, folder.getCreatedAt()); + Assert.assertEquals(modifiedAt, folder.getModifiedAt()); + Assert.assertEquals(description, folder.getDescription()); + Assert.assertEquals(size, folder.getSize()); + Assert.assertEquals(pathID, folder.getPathCollection().get(0).getID()); + Assert.assertEquals(pathSequenceID, folder.getPathCollection().get(0).getSequenceID()); + Assert.assertEquals(pathEtag, folder.getPathCollection().get(0).getEtag()); + Assert.assertEquals(pathName, folder.getPathCollection().get(0).getName()); + Assert.assertEquals(createdByID, folder.getCreatedBy().getID()); + Assert.assertEquals(createdByName, folder.getCreatedBy().getName()); + Assert.assertEquals(createdByLogin, folder.getCreatedBy().getLogin()); + Assert.assertEquals(modifiedByID, folder.getModifiedBy().getID()); + Assert.assertEquals(modifiedByName, folder.getModifiedBy().getName()); + Assert.assertEquals(modifiedByLogin, folder.getModifiedBy().getLogin()); + Assert.assertEquals(ownedByID, folder.getOwnedBy().getID()); + Assert.assertEquals(ownedByName, folder.getOwnedBy().getName()); + Assert.assertEquals(ownedByLogin, folder.getOwnedBy().getLogin()); + Assert.assertEquals(url, folder.getSharedLink().getURL()); + Assert.assertEquals(downloadURL, folder.getSharedLink().getDownloadURL()); + Assert.assertEquals(vanityUrl, folder.getSharedLink().getVanityURL()); + Assert.assertEquals(isPasswordEnabled, folder.getSharedLink().getIsPasswordEnabled()); + Assert.assertEquals(unsharedAt, folder.getSharedLink().getUnsharedDate()); + Assert.assertEquals(downloadCount, folder.getSharedLink().getDownloadCount()); + Assert.assertEquals(previewCount, folder.getSharedLink().getPreviewCount()); + Assert.assertEquals(access, folder.getSharedLink().getAccess()); + Assert.assertEquals(canDownload, folder.getSharedLink().getPermissions().getCanDownload()); + Assert.assertEquals(canPreview, folder.getSharedLink().getPermissions().getCanPreview()); + Assert.assertEquals(folderUploadEmailAccess, folder.getUploadEmail().getAccess()); + Assert.assertEquals(folderUploadEmail, folder.getUploadEmail().getEmail()); + Assert.assertEquals(parentID, folder.getParent().getID()); + Assert.assertEquals(parentSequenceID, folder.getParent().getSequenceID()); + Assert.assertEquals(parentEtag, folder.getParent().getEtag()); + Assert.assertEquals(parentName, folder.getParent().getName()); + Assert.assertEquals(itemStatus, folder.getItemStatus()); + } + @Test @Category(IntegrationTest.class) public void getCurrentUserInfoIsCorrect() throws InterruptedException { From add5aeb5d017a7c88f78c968dfceec37ba5ee99a Mon Sep 17 00:00:00 2001 From: David Maynard Date: Thu, 1 Dec 2016 11:20:59 -0800 Subject: [PATCH 026/119] Changed continuation indent fro 8 to 4. Whitespace change only --- src/test/java/com/box/sdk/BoxFolderTest.java | 198 +++++++++---------- 1 file changed, 99 insertions(+), 99 deletions(-) diff --git a/src/test/java/com/box/sdk/BoxFolderTest.java b/src/test/java/com/box/sdk/BoxFolderTest.java index b5db2b8fa..7f7bf8a34 100644 --- a/src/test/java/com/box/sdk/BoxFolderTest.java +++ b/src/test/java/com/box/sdk/BoxFolderTest.java @@ -74,11 +74,11 @@ public void createFolderSendsRequestWithRequiredFields() { String createdFolderName = "[createFolderSendsRequestWithRequiredFields] Child Folder"; stubFor(post(urlMatching("/folders")) - .withRequestBody(equalToJson("{ \"name\": \"" + createdFolderName + "\", \"parent\": {\"id\": \"" - + parentFolderID + "\"} }", LENIENT)) - .willReturn(aResponse() - .withHeader("Content-Type", "application/json") - .withBody("{\"id\": \"0\"}"))); + .withRequestBody(equalToJson("{ \"name\": \"" + createdFolderName + "\", \"parent\": {\"id\": \"" + + parentFolderID + "\"} }", LENIENT)) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json") + .withBody("{\"id\": \"0\"}"))); rootFolder.createFolder(createdFolderName); } @@ -90,7 +90,7 @@ public void infoParsesMixedPermissionsCorrectly() { String id = "id"; EnumSet expectedPermissions = EnumSet.of(BoxFolder.Permission.CAN_UPLOAD, - BoxFolder.Permission.CAN_DELETE, BoxFolder.Permission.CAN_INVITE_COLLABORATOR); + BoxFolder.Permission.CAN_DELETE, BoxFolder.Permission.CAN_INVITE_COLLABORATOR); JsonObject permissionsJSON = new JsonObject(); permissionsJSON.add("can_download", false); @@ -119,13 +119,13 @@ public void getChildrenRangeRequestsCorrectOffsetLimitAndFields() { api.setBaseURL("http://localhost:8080/"); stubFor(get(urlPathEqualTo("/folders/0/items/")) - .withQueryParam("offset", WireMock.equalTo("1")) - .withQueryParam("limit", WireMock.equalTo("2")) - .withQueryParam("fields", containing("name")) - .withQueryParam("fields", containing("description")) - .willReturn(aResponse() - .withHeader("Content-Type", "application/json") - .withBody("{\"total_count\": 3, \"entries\":[]}"))); + .withQueryParam("offset", WireMock.equalTo("1")) + .withQueryParam("limit", WireMock.equalTo("2")) + .withQueryParam("fields", containing("name")) + .withQueryParam("fields", containing("description")) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json") + .withBody("{\"total_count\": 3, \"entries\":[]}"))); BoxFolder rootFolder = BoxFolder.getRootFolder(api); PartialCollection children = rootFolder.getChildrenRange(1, 2, "name", "description"); @@ -143,8 +143,8 @@ public void collaborateShouldSendCorrectJSONWhenCollaboratingWithAGroup() { final BoxCollaboration.Role role = BoxCollaboration.Role.CO_OWNER; final JsonObject fakeJSONResponse = new JsonObject() - .add("type", "collaboration") - .add("id", "0"); + .add("type", "collaboration") + .add("id", "0"); BoxAPIConnection api = new BoxAPIConnection(""); api.setRequestInterceptor(new JSONRequestInterceptor() { @@ -181,15 +181,15 @@ public void getCollaborationsShouldParseGroupsCorrectly() { final String groupName = "non-empty name"; final JsonObject fakeJSONResponse = new JsonObject() - .add("total_count", 1) - .add("entries", new JsonArray() - .add(new JsonObject() - .add("type", "collaboration") - .add("id", "non-empty ID") - .add("accessible_by", new JsonObject() - .add("type", "group") - .add("id", groupID) - .add("name", groupName)))); + .add("total_count", 1) + .add("entries", new JsonArray() + .add(new JsonObject() + .add("type", "collaboration") + .add("id", "non-empty ID") + .add("accessible_by", new JsonObject() + .add("type", "group") + .add("id", groupID) + .add("name", groupName)))); BoxAPIConnection api = new BoxAPIConnection(""); api.setRequestInterceptor(new RequestInterceptor() { @@ -226,7 +226,7 @@ public void testGetWatermarkSendsCorrectRequest() { @Override public BoxAPIResponse onRequest(BoxAPIRequest request) { Assert.assertEquals("https://api.box.com/2.0/folders/0/watermark", - request.getUrl().toString()); + request.getUrl().toString()); return new BoxJSONResponse() { @Override public String getJSON() { @@ -249,11 +249,11 @@ public void testGetWatermarkParseAllFieldsCorrectly() throws ParseException { final Date modifiedAt = BoxDateFormat.parse("2016-11-31T15:33:33-07:00"); final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" - + " \"watermark\": {\n" - + " \"created_at\": \"2016-10-31T15:33:33-07:00\",\n" - + " \"modified_at\": \"2016-11-31T15:33:33-07:00\"\n" - + " }\n" - + "}"); + + " \"watermark\": {\n" + + " \"created_at\": \"2016-10-31T15:33:33-07:00\",\n" + + " \"modified_at\": \"2016-11-31T15:33:33-07:00\"\n" + + " }\n" + + "}"); BoxAPIConnection api = new BoxAPIConnection(""); api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); @@ -276,7 +276,7 @@ public void testApplyWatermarkSendsCorrectJson() { @Override protected BoxAPIResponse onJSONRequest(BoxJSONRequest request, JsonObject json) { Assert.assertEquals("https://api.box.com/2.0/folders/0/watermark", - request.getUrl().toString()); + request.getUrl().toString()); Assert.assertEquals(imprint, json.get("watermark").asObject().get("imprint").asString()); return new BoxJSONResponse() { @Override @@ -300,11 +300,11 @@ public void testApplyWatermarkParseAllFieldsCorrectly() throws ParseException { final Date modifiedAt = BoxDateFormat.parse("2016-11-31T15:33:33-07:00"); final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" - + " \"watermark\": {\n" - + " \"created_at\": \"2016-10-31T15:33:33-07:00\",\n" - + " \"modified_at\": \"2016-11-31T15:33:33-07:00\"\n" - + " }\n" - + "}"); + + " \"watermark\": {\n" + + " \"created_at\": \"2016-10-31T15:33:33-07:00\",\n" + + " \"modified_at\": \"2016-11-31T15:33:33-07:00\"\n" + + " }\n" + + "}"); BoxAPIConnection api = new BoxAPIConnection(""); api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); @@ -325,7 +325,7 @@ public void testRemoveWatermarkSendsCorrectRequest() { @Override public BoxAPIResponse onRequest(BoxAPIRequest request) { Assert.assertEquals("https://api.box.com/2.0/folders/0/watermark", - request.getUrl().toString()); + request.getUrl().toString()); return new BoxJSONResponse() { @Override public String getJSON() { @@ -350,8 +350,8 @@ public void testCreateWeblinkSendsCorrectJsonWithNameAndDescription() throws Mal final String description = "non-empty description"; final JsonObject fakeJSONResponse = new JsonObject() - .add("type", "web_link") - .add("id", "0"); + .add("type", "web_link") + .add("id", "0"); BoxAPIConnection api = new BoxAPIConnection(""); api.setRequestInterceptor(new RequestInterceptor() { @@ -388,8 +388,8 @@ public void testCreateWeblinkSendsCorrectJsonWithoutNameAndDescription() throws final String parentFolderID = "0"; final JsonObject fakeJSONResponse = new JsonObject() - .add("type", "web_link") - .add("id", "0"); + .add("type", "web_link") + .add("id", "0"); BoxAPIConnection api = new BoxAPIConnection(""); api.setRequestInterceptor(new RequestInterceptor() { @@ -448,57 +448,57 @@ public void testCreateWeblinkParseAllFieldsCorrectly() throws ParseException, Ma final String ownerLogin = "ted+demo@box.com"; final JsonObject fakeJSONResponse = JsonObject.readFrom("{\n" - + " \"type\": \"web_link\",\n" - + " \"id\": \"6742981\",\n" - + " \"sequence_id\": \"0\",\n" - + " \"etag\": \"0\",\n" - + " \"name\": \"Box Website\",\n" - + " \"url\": \"https://www.box.com\",\n" - + " \"created_by\": {\n" - + " \"type\": \"user\",\n" - + " \"id\": \"10523870\",\n" - + " \"name\": \"Ted Blosser\",\n" - + " \"login\": \"ted+demo@box.com\"\n" - + " },\n" - + " \"created_at\": \"2015-05-07T14:31:16-07:00\",\n" - + " \"modified_at\": \"2015-05-07T14:31:16-07:00\",\n" - + " \"parent\": {\n" - + " \"type\": \"folder\",\n" - + " \"id\": \"848123342\",\n" - + " \"sequence_id\": \"1\",\n" - + " \"etag\": \"1\",\n" - + " \"name\": \"Documentation\"\n" - + " },\n" - + " \"description\": \"Cloud Content Management\",\n" - + " \"item_status\": \"active\",\n" - + " \"trashed_at\": null,\n" - + " \"purged_at\": null,\n" - + " \"shared_link\": null,\n" - + " \"path_collection\": {\n" - + " \"total_count\": 1,\n" - + " \"entries\": [\n" - + " {\n" - + " \"type\": \"folder\",\n" - + " \"id\": \"848123342\",\n" - + " \"sequence_id\": \"1\",\n" - + " \"etag\": \"1\",\n" - + " \"name\": \"Documentation\"\n" - + " }\n" - + " ]\n" - + " },\n" - + " \"modified_by\": {\n" - + " \"type\": \"user\",\n" - + " \"id\": \"10523870\",\n" - + " \"name\": \"Ted Blosser\",\n" - + " \"login\": \"ted+demo@box.com\"\n" - + " },\n" - + " \"owned_by\": {\n" - + " \"type\": \"user\",\n" - + " \"id\": \"10523870\",\n" - + " \"name\": \"Ted Blosser\",\n" - + " \"login\": \"ted+demo@box.com\"\n" - + " }\n" - + "}"); + + " \"type\": \"web_link\",\n" + + " \"id\": \"6742981\",\n" + + " \"sequence_id\": \"0\",\n" + + " \"etag\": \"0\",\n" + + " \"name\": \"Box Website\",\n" + + " \"url\": \"https://www.box.com\",\n" + + " \"created_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"10523870\",\n" + + " \"name\": \"Ted Blosser\",\n" + + " \"login\": \"ted+demo@box.com\"\n" + + " },\n" + + " \"created_at\": \"2015-05-07T14:31:16-07:00\",\n" + + " \"modified_at\": \"2015-05-07T14:31:16-07:00\",\n" + + " \"parent\": {\n" + + " \"type\": \"folder\",\n" + + " \"id\": \"848123342\",\n" + + " \"sequence_id\": \"1\",\n" + + " \"etag\": \"1\",\n" + + " \"name\": \"Documentation\"\n" + + " },\n" + + " \"description\": \"Cloud Content Management\",\n" + + " \"item_status\": \"active\",\n" + + " \"trashed_at\": null,\n" + + " \"purged_at\": null,\n" + + " \"shared_link\": null,\n" + + " \"path_collection\": {\n" + + " \"total_count\": 1,\n" + + " \"entries\": [\n" + + " {\n" + + " \"type\": \"folder\",\n" + + " \"id\": \"848123342\",\n" + + " \"sequence_id\": \"1\",\n" + + " \"etag\": \"1\",\n" + + " \"name\": \"Documentation\"\n" + + " }\n" + + " ]\n" + + " },\n" + + " \"modified_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"10523870\",\n" + + " \"name\": \"Ted Blosser\",\n" + + " \"login\": \"ted+demo@box.com\"\n" + + " },\n" + + " \"owned_by\": {\n" + + " \"type\": \"user\",\n" + + " \"id\": \"10523870\",\n" + + " \"name\": \"Ted Blosser\",\n" + + " \"login\": \"ted+demo@box.com\"\n" + + " }\n" + + "}"); BoxAPIConnection api = new BoxAPIConnection(""); api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); @@ -546,7 +546,7 @@ public void creatingAndDeletingFolderSucceeds() { BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); BoxFolder rootFolder = BoxFolder.getRootFolder(api); BoxFolder childFolder = rootFolder.createFolder("[creatingAndDeletingFolderSucceeds] Ĥȅľľő Ƒŕőďő") - .getResource(); + .getResource(); assertThat(rootFolder, hasItem(Matchers.hasProperty("ID", equalTo(childFolder.getID())))); @@ -659,8 +659,8 @@ public void uploadFileWithCreatedAndModifiedDatesSucceeds() { final String fileContent = "Test file"; InputStream stream = new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8)); FileUploadParams params = new FileUploadParams() - .setName("[uploadFileWithCreatedAndModifiedDatesSucceeds] Test File.txt").setContent(stream) - .setModified(modified).setCreated(created); + .setName("[uploadFileWithCreatedAndModifiedDatesSucceeds] Test File.txt").setContent(stream) + .setModified(modified).setCreated(created); BoxFile.Info info = rootFolder.uploadFile(params); BoxFile uploadedFile = info.getResource(); @@ -834,7 +834,7 @@ public void getSharedItemAndItsChildrenSucceeds() { assertThat(sharedItem.getID(), is(equalTo(folder.getID()))); assertThat(sharedItem.getResource(), hasItem(Matchers.hasProperty("ID", - equalTo(childFolder.getID())))); + equalTo(childFolder.getID())))); folder.delete(true); } @@ -846,7 +846,7 @@ public void createWebLinkSucceeds() throws MalformedURLException { BoxFolder rootFolder = BoxFolder.getRootFolder(api); BoxWebLink createdWebLink = rootFolder.createWebLink("[createWebLinkSucceeds] Test Web Link", - new URL("https://api.box.com"), "[createWebLinkSucceeds] Test Web Link").getResource(); + new URL("https://api.box.com"), "[createWebLinkSucceeds] Test Web Link").getResource(); assertThat(rootFolder, hasItem(Matchers.hasProperty("ID", equalTo(createdWebLink.getID())))); @@ -861,7 +861,7 @@ public void createWebLinkNoNameSucceeds() throws MalformedURLException { BoxFolder rootFolder = BoxFolder.getRootFolder(api); BoxWebLink createdWebLink = rootFolder.createWebLink(new URL("https://api.box.com"), - "[createWebLinkSucceeds] Test Web Link").getResource(); + "[createWebLinkSucceeds] Test Web Link").getResource(); assertThat(rootFolder, hasItem(Matchers.hasProperty("ID", equalTo(createdWebLink.getID())))); @@ -876,7 +876,7 @@ public void createWebLinkNoDescriptionSucceeds() throws MalformedURLException { BoxFolder rootFolder = BoxFolder.getRootFolder(api); BoxWebLink createdWebLink = rootFolder.createWebLink("[createWebLinkSucceeds] Test Web Link", - new URL("https://api.box.com")).getResource(); + new URL("https://api.box.com")).getResource(); assertThat(rootFolder, hasItem(Matchers.hasProperty("ID", equalTo(createdWebLink.getID())))); From 943e420f7fc8bc96171b45092765bafb2263618d Mon Sep 17 00:00:00 2001 From: David Maynard Date: Wed, 7 Dec 2016 15:13:45 -0800 Subject: [PATCH 027/119] Fixed warning for missing @return javadoc in Boxuser.java --- src/main/java/com/box/sdk/BoxUser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/box/sdk/BoxUser.java b/src/main/java/com/box/sdk/BoxUser.java index c5fa0f64d..9099d49b2 100644 --- a/src/main/java/com/box/sdk/BoxUser.java +++ b/src/main/java/com/box/sdk/BoxUser.java @@ -204,7 +204,7 @@ public static Iterable getAllEnterpriseOrExternalUsers(final BoxAP * @param userType The type of users we want to search with this request. * Valid values are 'managed' (enterprise users), 'external' or 'all' * @param fields the fields to retrieve. Leave this out for the standard fields. - * @return + * @return An iterator over the selected users. */ private static Iterable getUsersInfoForType(final BoxAPIConnection api, final String filterTerm, final String userType, final String... fields) { From bf6dd7267df3a9b48db558edb89a6c4cad37939d Mon Sep 17 00:00:00 2001 From: David Maynard Date: Wed, 7 Dec 2016 16:02:08 -0800 Subject: [PATCH 028/119] Adding sourceCompatibility = 1.6 and targetCompatibility = 1.6to gradle.build --- build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.gradle b/build.gradle index 1febe0b13..a4fd63fc3 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,11 @@ dependencies { testCompile 'org.slf4j:slf4j-nop:1.7.7' } +compileJava { + sourceCompatibility = 1.6 + targetCompatibility = 1.6 +} + javadoc { options.windowTitle 'Box Java SDK' options.noQualifiers 'all' From 757b384d915acee375233a2832a59eb51c29335b Mon Sep 17 00:00:00 2001 From: David Maynard Date: Wed, 7 Dec 2016 16:13:14 -0800 Subject: [PATCH 029/119] Fixing merge conflict in doc/retention_policies.md --- doc/retention_policies.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/doc/retention_policies.md b/doc/retention_policies.md index df6da1d24..408eb42ab 100644 --- a/doc/retention_policies.md +++ b/doc/retention_policies.md @@ -72,10 +72,7 @@ for (BoxRetentionPolicy.Info policyInfo : policies) { ``` [get-retention-policies]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxRetentionPolicy.html#getAll(com.box.sdk.BoxAPIConnection,%20java.lang.String...) -<<<<<<< HEAD -======= ->>>>>>> 7f379dfea0f0ceb84a179f6a062b6572db6e255b [get-retention-policies-with-fields]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxRetentionPolicy.html#getAll(java.lang.String,%20java.lang.String,%20java.lang.String,%20int,%20com.box.sdk.BoxAPIConnection,%20java.lang.String...) Get Retention Policy Assignments @@ -131,8 +128,6 @@ BoxRetentionPolicyAssignment.Info assignmentInfo = assignment.getInfo("assigned_ [get-assignment]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxRetentionPolicyAssignment.html#getInfo(java.lang.String...) -<<<<<<< HEAD -======= Get File Version Retention -------------- @@ -168,4 +163,3 @@ for (BoxFileVersionRetention.Info retentionInfo : retentions) { [get-all-file-version-retentions]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFileVersionRetention.html#getInfo(com.box.sdk.BoxAPIConnection,%20java.lang.String...) [query-filter]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFileVersionRetention.html#QueryFilter [get-all-file-version-retentions-with-filter]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFileVersionRetention.html#getInfo(com.box.sdk.BoxAPIConnection,%20com.box.sdk.BoxFileVersionRetention.QueryFilter,%20java.lang.String...) ->>>>>>> 7f379dfea0f0ceb84a179f6a062b6572db6e255b From f9de883252990c986dbc5a4bfd68dbccba6f5d99 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Wed, 7 Dec 2016 17:55:57 -0800 Subject: [PATCH 030/119] Refactored BoxLegalHold to be BoxLegalHoldPolicy to match api documents --- .../com/box/sdk/BoxLegalHoldAssignment.java | 6 +- ...LegalHold.java => BoxLegalHoldPolicy.java} | 30 ++++---- src/main/java/com/box/sdk/BoxResource.java | 2 +- ... => BoxLegalHoldPolicyAssignmentTest.java} | 2 +- ...dTest.java => BoxLegalHoldPolicyTest.java} | 77 ++++++++++--------- 5 files changed, 59 insertions(+), 58 deletions(-) rename src/main/java/com/box/sdk/{BoxLegalHold.java => BoxLegalHoldPolicy.java} (93%) rename src/test/java/com/box/sdk/{BoxLegalHoldAssignmentTest.java => BoxLegalHoldPolicyAssignmentTest.java} (99%) rename src/test/java/com/box/sdk/{BoxLegalHoldTest.java => BoxLegalHoldPolicyTest.java} (89%) diff --git a/src/main/java/com/box/sdk/BoxLegalHoldAssignment.java b/src/main/java/com/box/sdk/BoxLegalHoldAssignment.java index 4d442ef2f..1c24c6305 100644 --- a/src/main/java/com/box/sdk/BoxLegalHoldAssignment.java +++ b/src/main/java/com/box/sdk/BoxLegalHoldAssignment.java @@ -121,7 +121,7 @@ public class Info extends BoxResource.Info { /** * @see #getLegalHold() */ - private BoxLegalHold.Info legalHold; + private BoxLegalHoldPolicy.Info legalHold; /** * @see #getAssignedBy() @@ -182,7 +182,7 @@ public BoxResource getResource() { /** * @return info about the policy that this legal hold policy assignment is part of. */ - public BoxLegalHold.Info getLegalHold() { + public BoxLegalHoldPolicy.Info getLegalHold() { return this.legalHold; } @@ -234,7 +234,7 @@ void parseJSONMember(JsonObject.Member member) { JsonObject policyJSON = value.asObject(); if (this.legalHold == null) { String policyID = policyJSON.get("id").asString(); - BoxLegalHold policy = new BoxLegalHold(getAPI(), policyID); + BoxLegalHoldPolicy policy = new BoxLegalHoldPolicy(getAPI(), policyID); this.legalHold = policy.new Info(policyJSON); } else { this.legalHold.update(policyJSON); diff --git a/src/main/java/com/box/sdk/BoxLegalHold.java b/src/main/java/com/box/sdk/BoxLegalHoldPolicy.java similarity index 93% rename from src/main/java/com/box/sdk/BoxLegalHold.java rename to src/main/java/com/box/sdk/BoxLegalHoldPolicy.java index 65fc2b74f..1a8bb1957 100644 --- a/src/main/java/com/box/sdk/BoxLegalHold.java +++ b/src/main/java/com/box/sdk/BoxLegalHoldPolicy.java @@ -18,7 +18,7 @@ * handling for errors related to the Box REST API, you should capture this exception explicitly.

*/ @BoxResourceType("legal_hold") -public class BoxLegalHold extends BoxResource { +public class BoxLegalHoldPolicy extends BoxResource { private static final URLTemplate LEGAL_HOLD_URL_TEMPLATE = new URLTemplate("legal_hold_policies/%s"); private static final URLTemplate ALL_LEGAL_HOLD_URL_TEMPLATE = new URLTemplate("legal_hold_policies"); @@ -29,11 +29,11 @@ public class BoxLegalHold extends BoxResource { private static final int DEFAULT_LIMIT = 100; /** - * Constructs a BoxLegalHold for a resource with a given ID. + * Constructs a BoxLegalHoldPolicy for a resource with a given ID. * @param api the API connection to be used by the resource. * @param id the ID of the resource. */ - public BoxLegalHold(BoxAPIConnection api, String id) { + public BoxLegalHoldPolicy(BoxAPIConnection api, String id) { super(api, id); } @@ -60,7 +60,7 @@ public Info getInfo(String ... fields) { * @param name the name of Legal Hold Policy. * @return information about the Legal Hold Policy created. */ - public static BoxLegalHold.Info create(BoxAPIConnection api, String name) { + public static BoxLegalHoldPolicy.Info create(BoxAPIConnection api, String name) { return create(api, name, null, null, null); } @@ -73,8 +73,8 @@ public static BoxLegalHold.Info create(BoxAPIConnection api, String name) { * @param filterEndedAt optional date filter applies to Custodian assignments only. * @return information about the Legal Hold Policy created. */ - public static BoxLegalHold.Info create(BoxAPIConnection api, String name, String description, - Date filterStartedAt, Date filterEndedAt) { + public static BoxLegalHoldPolicy.Info create(BoxAPIConnection api, String name, String description, + Date filterStartedAt, Date filterEndedAt) { URL url = ALL_LEGAL_HOLD_URL_TEMPLATE.build(api.getBaseURL()); BoxJSONRequest request = new BoxJSONRequest(api, url, "POST"); JsonObject requestJSON = new JsonObject() @@ -91,7 +91,7 @@ public static BoxLegalHold.Info create(BoxAPIConnection api, String name, String request.setBody(requestJSON.toString()); BoxJSONResponse response = (BoxJSONResponse) request.send(); JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); - BoxLegalHold createdPolicy = new BoxLegalHold(api, responseJSON.get("id").asString()); + BoxLegalHoldPolicy createdPolicy = new BoxLegalHoldPolicy(api, responseJSON.get("id").asString()); return createdPolicy.new Info(responseJSON); } @@ -110,7 +110,7 @@ public void delete() { * Only policy_name, description and release_notes can be modified. * @param info the updated info. */ - public void updateInfo(BoxLegalHold.Info info) { + public void updateInfo(BoxLegalHoldPolicy.Info info) { URL url = LEGAL_HOLD_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT"); request.setBody(info.getPendingChanges()); @@ -124,7 +124,7 @@ public void updateInfo(BoxLegalHold.Info info) { * @param api api the API connection to be used by the resource. * @return the Iterable of Legal Hold Policies in your Enterprise. */ - public static Iterable getAll(final BoxAPIConnection api) { + public static Iterable getAll(final BoxAPIConnection api) { return getAll(api, null, DEFAULT_LIMIT); } @@ -136,7 +136,7 @@ public static Iterable getAll(final BoxAPIConnection api) { * @param fields the optional fields to retrieve. * @return the Iterable of Legal Hold Policies in your Enterprise that match the filter parameters. */ - public static Iterable getAll( + public static Iterable getAll( final BoxAPIConnection api, String policyName, int limit, String ... fields) { QueryStringBuilder builder = new QueryStringBuilder(); if (policyName != null) { @@ -145,13 +145,13 @@ public static Iterable getAll( if (fields.length > 0) { builder.appendParam("fields", fields); } - return new BoxResourceIterable(api, + return new BoxResourceIterable(api, ALL_LEGAL_HOLD_URL_TEMPLATE.buildWithQuery(api.getBaseURL(), builder.toString()), limit) { @Override - protected BoxLegalHold.Info factory(JsonObject jsonObject) { - BoxLegalHold policy = new BoxLegalHold(api, jsonObject.get("id").asString()); + protected BoxLegalHoldPolicy.Info factory(JsonObject jsonObject) { + BoxLegalHoldPolicy policy = new BoxLegalHoldPolicy(api, jsonObject.get("id").asString()); return policy.new Info(jsonObject); } }; @@ -205,7 +205,7 @@ public Iterable getAssignments(String type, String @Override protected BoxLegalHoldAssignment.Info factory(JsonObject jsonObject) { BoxLegalHoldAssignment assignment = new BoxLegalHoldAssignment( - BoxLegalHold.this.getAPI(), jsonObject.get("id").asString()); + BoxLegalHoldPolicy.this.getAPI(), jsonObject.get("id").asString()); return assignment.new Info(jsonObject); } }; @@ -347,7 +347,7 @@ public Info(String json) { */ @Override public BoxResource getResource() { - return BoxLegalHold.this; + return BoxLegalHoldPolicy.this; } /** diff --git a/src/main/java/com/box/sdk/BoxResource.java b/src/main/java/com/box/sdk/BoxResource.java index 15931ed51..1dc4299bb 100644 --- a/src/main/java/com/box/sdk/BoxResource.java +++ b/src/main/java/com/box/sdk/BoxResource.java @@ -57,7 +57,7 @@ private static Map> initResourceClassByType result.put(getResourceType(BoxRetentionPolicy.class), BoxRetentionPolicy.class); result.put(getResourceType(BoxRetentionPolicyAssignment.class), BoxRetentionPolicyAssignment.class); result.put(getResourceType(BoxFileVersionRetention.class), BoxFileVersionRetention.class); - result.put(getResourceType(BoxLegalHold.class), BoxLegalHold.class); + result.put(getResourceType(BoxLegalHoldPolicy.class), BoxLegalHoldPolicy.class); result.put(getResourceType(BoxLegalHoldAssignment.class), BoxLegalHoldAssignment.class); result.put(getResourceType(BoxFileVersionLegalHold.class), BoxFileVersionLegalHold.class); return Collections.unmodifiableMap(result); diff --git a/src/test/java/com/box/sdk/BoxLegalHoldAssignmentTest.java b/src/test/java/com/box/sdk/BoxLegalHoldPolicyAssignmentTest.java similarity index 99% rename from src/test/java/com/box/sdk/BoxLegalHoldAssignmentTest.java rename to src/test/java/com/box/sdk/BoxLegalHoldPolicyAssignmentTest.java index fd1f5367a..ce5520632 100644 --- a/src/test/java/com/box/sdk/BoxLegalHoldAssignmentTest.java +++ b/src/test/java/com/box/sdk/BoxLegalHoldPolicyAssignmentTest.java @@ -12,7 +12,7 @@ /** * {@link BoxLegalHoldAssignment} related unit tests. */ -public class BoxLegalHoldAssignmentTest { +public class BoxLegalHoldPolicyAssignmentTest { /** * Unit test for {@link BoxLegalHoldAssignment#getInfo(String...)} diff --git a/src/test/java/com/box/sdk/BoxLegalHoldTest.java b/src/test/java/com/box/sdk/BoxLegalHoldPolicyTest.java similarity index 89% rename from src/test/java/com/box/sdk/BoxLegalHoldTest.java rename to src/test/java/com/box/sdk/BoxLegalHoldPolicyTest.java index 963708575..27ae5c6ce 100644 --- a/src/test/java/com/box/sdk/BoxLegalHoldTest.java +++ b/src/test/java/com/box/sdk/BoxLegalHoldPolicyTest.java @@ -11,12 +11,12 @@ import com.eclipsesource.json.JsonObject; /** - * {@link BoxLegalHold} related unit tests. + * {@link BoxLegalHoldPolicy} related unit tests. */ -public class BoxLegalHoldTest { +public class BoxLegalHoldPolicyTest { /** - * Unit test for {@link BoxLegalHold#getInfo(String...)} + * Unit test for {@link BoxLegalHoldPolicy#getInfo(String...)} */ @Test @Category(UnitTest.class) @@ -36,12 +36,12 @@ public String getJSON() { } }); - BoxLegalHold policy = new BoxLegalHold(api, "0"); + BoxLegalHoldPolicy policy = new BoxLegalHoldPolicy(api, "0"); policy.getInfo("description", "status"); } /** - * Unit test for {@link BoxLegalHold#getInfo(String...)} + * Unit test for {@link BoxLegalHoldPolicy#getInfo(String...)} */ @Test @Category(UnitTest.class) @@ -93,8 +93,8 @@ public void testGetInfoParseAllFieldsCorrectly() throws ParseException { BoxAPIConnection api = new BoxAPIConnection(""); api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); - BoxLegalHold policy = new BoxLegalHold(api, id); - BoxLegalHold.Info info = policy.getInfo(); + BoxLegalHoldPolicy policy = new BoxLegalHoldPolicy(api, id); + BoxLegalHoldPolicy.Info info = policy.getInfo(); Assert.assertEquals(id, info.getID()); Assert.assertEquals(name, info.getPolicyName()); Assert.assertEquals(description, info.getDescription()); @@ -115,7 +115,7 @@ public void testGetInfoParseAllFieldsCorrectly() throws ParseException { } /** - * Unit test for {@link BoxLegalHold#create(BoxAPIConnection, String, String, Date, Date)} + * Unit test for {@link BoxLegalHoldPolicy#create(BoxAPIConnection, String, String, Date, Date)} */ @Test @Category(UnitTest.class) @@ -147,11 +147,11 @@ public String getJSON() { } }); - BoxLegalHold.create(api, name, description, startedAt, endedAt); + BoxLegalHoldPolicy.create(api, name, description, startedAt, endedAt); } /** - * Unit test for {@link BoxLegalHold#create(BoxAPIConnection, String)} + * Unit test for {@link BoxLegalHoldPolicy#create(BoxAPIConnection, String)} */ @Test @Category(UnitTest.class) @@ -189,7 +189,7 @@ public void testCreateParseAllFieldsCorrectly() throws ParseException { BoxAPIConnection api = new BoxAPIConnection(""); api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); - BoxLegalHold.Info info = BoxLegalHold.create(api, name); + BoxLegalHoldPolicy.Info info = BoxLegalHoldPolicy.create(api, name); Assert.assertEquals(id, info.getID()); Assert.assertEquals(name, info.getPolicyName()); Assert.assertEquals(description, info.getDescription()); @@ -205,7 +205,7 @@ public void testCreateParseAllFieldsCorrectly() throws ParseException { } /** - * Unit test for {@link BoxLegalHold#delete()} + * Unit test for {@link BoxLegalHoldPolicy#delete()} */ @Test @Category(UnitTest.class) @@ -224,12 +224,12 @@ public String getJSON() { } }); - BoxLegalHold policy = new BoxLegalHold(api, "0"); + BoxLegalHoldPolicy policy = new BoxLegalHoldPolicy(api, "0"); policy.delete(); } /** - * Unit test for {@link BoxLegalHold#updateInfo(BoxLegalHold.Info)} + * Unit test for {@link BoxLegalHoldPolicy#updateInfo(BoxLegalHoldPolicy.Info)} */ @Test @Category(UnitTest.class) @@ -255,8 +255,8 @@ public String getJSON() { } }); - BoxLegalHold policy = new BoxLegalHold(api, "0"); - BoxLegalHold.Info info = policy.new Info(); + BoxLegalHoldPolicy policy = new BoxLegalHoldPolicy(api, "0"); + BoxLegalHoldPolicy.Info info = policy.new Info(); info.addPendingChange("policy_name", name); info.addPendingChange("description", description); info.addPendingChange("release_note", note); @@ -264,7 +264,7 @@ public String getJSON() { } /** - * Unit test for {@link BoxLegalHold#updateInfo(BoxLegalHold.Info)} + * Unit test for {@link BoxLegalHoldPolicy#updateInfo(BoxLegalHoldPolicy.Info)} */ @Test @Category(UnitTest.class) @@ -302,8 +302,8 @@ public void testUpdateParseAllFieldsCorrectly() throws ParseException { BoxAPIConnection api = new BoxAPIConnection(""); api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); - BoxLegalHold policy = new BoxLegalHold(api, id); - BoxLegalHold.Info info = policy.new Info(); + BoxLegalHoldPolicy policy = new BoxLegalHoldPolicy(api, id); + BoxLegalHoldPolicy.Info info = policy.new Info(); info.addPendingChange("policy_name", name); policy.updateInfo(info); Assert.assertEquals(id, info.getID()); @@ -321,7 +321,7 @@ public void testUpdateParseAllFieldsCorrectly() throws ParseException { } /** - * Unit test for {@link BoxLegalHold#getAll(BoxAPIConnection)} + * Unit test for {@link BoxLegalHoldPolicy#getAll(BoxAPIConnection)} */ @Test @Category(UnitTest.class) @@ -341,12 +341,12 @@ public String getJSON() { } }); - Iterator iterator = BoxLegalHold.getAll(api).iterator(); + Iterator iterator = BoxLegalHoldPolicy.getAll(api).iterator(); iterator.hasNext(); } /** - * Unit test for {@link BoxLegalHold#getAll(BoxAPIConnection, String, int, String...)} + * Unit test for {@link BoxLegalHoldPolicy#getAll(BoxAPIConnection, String, int, String...)} */ @Test @Category(UnitTest.class) @@ -368,12 +368,13 @@ public String getJSON() { } }); - Iterator iterator = BoxLegalHold.getAll(api, "pol", 100, "description", "status").iterator(); + Iterator iterator = + BoxLegalHoldPolicy.getAll(api, "pol", 100, "description", "status").iterator(); iterator.hasNext(); } /** - * Unit test for {@link BoxLegalHold#getAll(BoxAPIConnection)} + * Unit test for {@link BoxLegalHoldPolicy#getAll(BoxAPIConnection)} */ @Test @Category(UnitTest.class) @@ -408,8 +409,8 @@ public void testGetAllParseAllFieldsCorrectly() { BoxAPIConnection api = new BoxAPIConnection(""); api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); - Iterator iterator = BoxLegalHold.getAll(api).iterator(); - BoxLegalHold.Info info = iterator.next(); + Iterator iterator = BoxLegalHoldPolicy.getAll(api).iterator(); + BoxLegalHoldPolicy.Info info = iterator.next(); Assert.assertEquals(firstPolicyID, info.getID()); Assert.assertEquals(firstPolicyName, info.getPolicyName()); info = iterator.next(); @@ -419,7 +420,7 @@ public void testGetAllParseAllFieldsCorrectly() { } /** - * Unit test for {@link BoxLegalHold#assignTo(BoxResource)} + * Unit test for {@link BoxLegalHoldPolicy#assignTo(BoxResource)} */ @Test @Category(UnitTest.class) @@ -446,13 +447,13 @@ public String getJSON() { } }); - BoxLegalHold policy = new BoxLegalHold(api, "0"); + BoxLegalHoldPolicy policy = new BoxLegalHoldPolicy(api, "0"); BoxFileVersion version = new BoxFileVersion(api, "{\"id\": \"1\"}", "2"); policy.assignTo(version); } /** - * Unit test for {@link BoxLegalHold#getAssignments(String...)} + * Unit test for {@link BoxLegalHoldPolicy#getAssignments(String...)} */ @Test @Category(UnitTest.class) @@ -473,13 +474,13 @@ public String getJSON() { } }); - BoxLegalHold policy = new BoxLegalHold(api, "0"); + BoxLegalHoldPolicy policy = new BoxLegalHoldPolicy(api, "0"); Iterator iterator = policy.getAssignments("assigned_at").iterator(); iterator.hasNext(); } /** - * Unit test for {@link BoxLegalHold#getAssignments(String, String, int, String...)} + * Unit test for {@link BoxLegalHoldPolicy#getAssignments(String, String, int, String...)} */ @Test @Category(UnitTest.class) @@ -501,14 +502,14 @@ public String getJSON() { } }); - BoxLegalHold policy = new BoxLegalHold(api, "0"); + BoxLegalHoldPolicy policy = new BoxLegalHoldPolicy(api, "0"); Iterator iterator = policy.getAssignments(BoxResource.getResourceType(BoxFolder.class), "1", 99).iterator(); iterator.hasNext(); } /** - * Unit test for {@link BoxLegalHold#getAssignments(String...)} + * Unit test for {@link BoxLegalHoldPolicy#getAssignments(String...)} */ @Test @Category(UnitTest.class) @@ -539,7 +540,7 @@ public void testGetAssignmentsParseAllFieldsCorrectly() { BoxAPIConnection api = new BoxAPIConnection(""); api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); - BoxLegalHold policy = new BoxLegalHold(api, "0"); + BoxLegalHoldPolicy policy = new BoxLegalHoldPolicy(api, "0"); Iterator iterator = policy.getAssignments().iterator(); BoxLegalHoldAssignment.Info info = iterator.next(); Assert.assertEquals(firstEntryID, info.getID()); @@ -549,7 +550,7 @@ public void testGetAssignmentsParseAllFieldsCorrectly() { } /** - * Unit test for {@link BoxLegalHold#getFileVersionHolds(String...)} + * Unit test for {@link BoxLegalHoldPolicy#getFileVersionHolds(String...)} */ @Test @Category(UnitTest.class) @@ -569,13 +570,13 @@ public String getJSON() { } }); - BoxLegalHold policy = new BoxLegalHold(api, "0"); + BoxLegalHoldPolicy policy = new BoxLegalHoldPolicy(api, "0"); Iterator iterator = policy.getFileVersionHolds().iterator(); iterator.hasNext(); } /** - * Unit test for {@link BoxLegalHold#getFileVersionHolds(String...)} + * Unit test for {@link BoxLegalHoldPolicy#getFileVersionHolds(String...)} */ @Test @Category(UnitTest.class) @@ -611,7 +612,7 @@ public void testGetFileVersionHoldsParseAllFieldsCorrectly() { BoxAPIConnection api = new BoxAPIConnection(""); api.setRequestInterceptor(JSONRequestInterceptor.respondWith(fakeJSONResponse)); - BoxLegalHold policy = new BoxLegalHold(api, "0"); + BoxLegalHoldPolicy policy = new BoxLegalHoldPolicy(api, "0"); Iterator iterator = policy.getFileVersionHolds().iterator(); Assert.assertEquals(firstID, iterator.next().getID()); Assert.assertEquals(secondID, iterator.next().getID()); From 0f6e888770b9e8e095980f6bdbe37fb836027be2 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Mon, 12 Dec 2016 18:13:53 -0800 Subject: [PATCH 031/119] Updated Object name form BoxLegalHold to BoLegalHoldPolicy in ReadMe --- doc/legal_holds.md | 50 +++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/doc/legal_holds.md b/doc/legal_holds.md index d5c5c6408..5077c0567 100644 --- a/doc/legal_holds.md +++ b/doc/legal_holds.md @@ -19,16 +19,16 @@ such as name, description, and filter dates. Get Legal Hold Policy --------------------- -Calling [`getInfo(String...)`][get-info] will return a BoxLegalHold.Info object +Calling [`getInfo(String...)`][get-info] will return a BoxLegalHoldPolicy.Info object containing information about the legal hold policy. If necessary to retrieve limited set of fields, it is possible to specify them using param. ```java -BoxLegalHold policy = new BoxLegalHold(api, id); -BoxLegalHold.Info policyInfo = policy.getInfo(); +BoxLegalHoldPolicy policy = new BoxLegalHoldPolicy(api, id); +BoxLegalHoldPolicy.Info policyInfo = policy.getInfo(); ``` -[get-info]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHold.html#getInfo(java.lang.String...) +[get-info]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHoldPolicy.html#getInfo(java.lang.String...) Get List of Legal Hold Policies ------------------------------- @@ -40,14 +40,14 @@ response and fields to retrieve by calling the static [`getAll(BoxAPIConnection, String, int, String...)`][get-list-of-legal-hold-policies-with-fields] method. ```java -Iterable policies = BoxLegalHold.getAll(api); -for (BoxLegalHold.Info policyInfo : policies) { +Iterable policies = BoxLegalHoldPolicy.getAll(api); +for (BoxLegalHoldPolicy.Info policyInfo : policies) { // Do something with the legal hold policy. } ``` -[get-list-of-legal-hold-policies]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHold.html#getAll(com.box.sdk.BoxAPIConnection) -[get-list-of-legal-hold-policies-with-fields]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHold.html#getAll(com.box.sdk.BoxAPIConnection,%20java.lang.String,%20int,%20java.lang.String...) +[get-list-of-legal-hold-policies]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHoldPolicy.html#getAll(com.box.sdk.BoxAPIConnection) +[get-list-of-legal-hold-policies-with-fields]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHoldPolicy.html#getAll(com.box.sdk.BoxAPIConnection,%20java.lang.String,%20int,%20java.lang.String...) Create New Legal Hold Policy ---------------------------- @@ -59,26 +59,26 @@ static method will let you create a new legal hold policy with a specified name, description, start and end dates. ```java -BoxLegalHold.Info policyInfo = BoxLegalHold.create(api, name, description, startedAt, endedAt); +BoxLegalHoldPolicy.Info policyInfo = BoxLegalHoldPolicy.create(api, name, description, startedAt, endedAt); ``` -[create-new-legal-hold-policy]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHold.html#create(com.box.sdk.BoxAPIConnection,%20java.lang.String) -[create-new-legal-hold-policy-with-dates]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHold.html#create(com.box.sdk.BoxAPIConnection,%20java.lang.String,%20java.lang.String,%20java.util.Date,%20java.util.Date) +[create-new-legal-hold-policy]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHoldPolicy.html#create(com.box.sdk.BoxAPIConnection,%20java.lang.String) +[create-new-legal-hold-policy-with-dates]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHoldPolicy.html#create(com.box.sdk.BoxAPIConnection,%20java.lang.String,%20java.lang.String,%20java.util.Date,%20java.util.Date) Update Existing Legal Hold Policy --------------------------------- Updating a legal hold policy's information is done by calling -[`updateInfo(BoxLegalHold.Info)`][update-info]. +[`updateInfo(BoxLegalHoldPolicy.Info)`][update-info]. ```java -BoxLegalHold policy = new BoxLegalHold(api, id); -BoxLegalHold.Info policyInfo = policy.new Info(); +BoxLegalHoldPolicy policy = new BoxLegalHoldPolicy(api, id); +BoxLegalHoldPolicy.Info policyInfo = policy.new Info(); info.addPendingChange("description", "new description"); policy.updateInfo(info); ``` -[update-info]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHold.html#update(com.box.sdk.BoxLegalHold.Info) +[update-info]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHoldPolicy.html#update(com.box.sdk.BoxLegalHoldPolicy.Info) Delete Legal Hold Policy ------------------------ @@ -86,11 +86,11 @@ Delete Legal Hold Policy A legal hold policy can be deleted by calling the [`delete()`][delete] method. ```java -BoxLegalHold policy = new BoxLegalHold(api, id); +BoxLegalHoldPolicy policy = new BoxLegalHoldPolicy(api, id); policy.delete(); ``` -[delete]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHold.html#delete() +[delete]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHoldPolicy.html#delete() Get Assignment -------------- @@ -114,15 +114,15 @@ It is possible to specify filters for type and id, maximum number of items per s response and fields to retrieve by calling [`getAssignments(String, String, int, String...)`][get-list-of-assignments-with-params]. ```java -BoxLegalHold policy = new BoxLegalHold(api, id); +BoxLegalHoldPolicy policy = new BoxLegalHoldPolicy(api, id); Iterable assignments = policy.getAssignments(BoxResource.getResourceType(BoxFolder.class), null, 50, "assigned_at"); for (BoxLegalHoldAssignment.Info assignmentInfo : assignments) { // Do something with the legal hold policy assignment. } ``` -[get-list-of-assignments]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHold.html#getAssignments(java.lang.String...) -[get-list-of-assignments-with-params]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHold.html#getAssignments(java.lang.String,%20java.lang.String,%20int,%20java.lang.String...) +[get-list-of-assignments]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHoldPolicy.html#getAssignments(java.lang.String...) +[get-list-of-assignments-with-params]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHoldPolicy.html#getAssignments(java.lang.String,%20java.lang.String,%20int,%20java.lang.String...) Create New Assignment -------------- @@ -131,12 +131,12 @@ To create new legal hold policy assignment call [`assignTo(BoxResource)`][create Currently only BoxFile, BoxFileVersion, BoxFolder and BoxUser objects are supported as a parameter. ```java -BoxLegalHold policy = new BoxLegalHold(api, policyID); +BoxLegalHoldPolicy policy = new BoxLegalHoldPolicy(api, policyID); BoxFolder folder = new BoxFolder(api, folderID); policy.assignTo(folder); ``` -[create-assignment]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHold.html#assignTo(com.box.sdk.BoxResource) +[create-assignment]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHoldPolicy.html#assignTo(com.box.sdk.BoxResource) Delete Assignment -------------- @@ -171,12 +171,12 @@ legal hold policy, call [`getFileVersionHolds(String...)`][get-lest-of-file-vers It is possible to specify maximum number of items per single response by calling [`getFileVersionHolds(int, String...)`][get-lest-of-file-version-legal-holds-with-limit]. ```java -BoxLegalHold policy = new BoxLegalHold(api, id); +BoxLegalHoldPolicy policy = new BoxLegalHoldPolicy(api, id); Iterable fileVersionHolds = policy.getFileVersionHolds(); for (BoxFileVersionLegalHold.Info fileVersionHold : fileVersionHolds) { // Do something with the file version legal hold. } ``` -[get-lest-of-file-version-legal-holds]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHold.html#getFileVersionHolds(java.lang.String...) -[get-lest-of-file-version-legal-holds-with-limit]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHold.html#getFileVersionHolds(int,%20java.lang.String...) \ No newline at end of file +[get-lest-of-file-version-legal-holds]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHoldPolicy.html#getFileVersionHolds(java.lang.String...) +[get-lest-of-file-version-legal-holds-with-limit]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxLegalHoldPolicy.html#getFileVersionHolds(int,%20java.lang.String...) From 0ace5ee90e3eb7d35a3dfface8a9a254479457cf Mon Sep 17 00:00:00 2001 From: David Maynard Date: Mon, 12 Dec 2016 18:27:22 -0800 Subject: [PATCH 032/119] Removing Java 1.6 Source Compatibility Check (it seems to break Travis) --- build.gradle | 5 ----- 1 file changed, 5 deletions(-) diff --git a/build.gradle b/build.gradle index a4fd63fc3..1febe0b13 100644 --- a/build.gradle +++ b/build.gradle @@ -28,11 +28,6 @@ dependencies { testCompile 'org.slf4j:slf4j-nop:1.7.7' } -compileJava { - sourceCompatibility = 1.6 - targetCompatibility = 1.6 -} - javadoc { options.windowTitle 'Box Java SDK' options.noQualifiers 'all' From e66ded7b29dad157efbb6150dd88d717d2aa3888 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Tue, 13 Dec 2016 13:23:18 -0800 Subject: [PATCH 033/119] Adding sourceCompatibility = 1.6 back in gradle.build --- build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.gradle b/build.gradle index 1febe0b13..a4fd63fc3 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,11 @@ dependencies { testCompile 'org.slf4j:slf4j-nop:1.7.7' } +compileJava { + sourceCompatibility = 1.6 + targetCompatibility = 1.6 +} + javadoc { options.windowTitle 'Box Java SDK' options.noQualifiers 'all' From 958b2ff38c03d174c62599996d8337c83561b6c1 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Tue, 20 Dec 2016 17:56:54 -0800 Subject: [PATCH 034/119] Fix bug in passing SHA1 in uploadVersion --- src/main/java/com/box/sdk/BoxFile.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index a78b32d0b..48fbd7b68 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -457,7 +457,7 @@ public void uploadVersion(InputStream fileContent) { * */ public void uploadVersion(InputStream fileContent, String fileContentSHA1) { - this.uploadVersion(fileContent, null, null); + this.uploadVersion(fileContent, fileContentSHA1, null); } /** From 5204b047a369a0afb9390ed0dc1045d0bd497243 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Wed, 21 Dec 2016 10:51:29 -0800 Subject: [PATCH 035/119] Updated version to 3.0.0-SNAPSHOT --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a4fd63fc3..ed2628bd8 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ sourceCompatibility = 1.6 group = 'com.box' archivesBaseName = 'box-java-sdk' -version = '2.2.2-SNAPSHOT' +version = '3.0.0-SNAPSHOT' repositories { mavenCentral() From e0376474d56b94f937b8b4e2c0064277138dc306 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Wed, 21 Dec 2016 10:54:01 -0800 Subject: [PATCH 036/119] Changing version to 2.2.3-SNAPSHOT (trying to fix sonatype error) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ed2628bd8..672baf262 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ sourceCompatibility = 1.6 group = 'com.box' archivesBaseName = 'box-java-sdk' -version = '3.0.0-SNAPSHOT' +version = '2.2.3-SNAPSHOT' repositories { mavenCentral() From 41c4763233b9979dced62f0f2be7083a2314b9a2 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Wed, 4 Jan 2017 16:54:16 -0800 Subject: [PATCH 037/119] Updating Version # to 2.3.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 672baf262..7f51765e1 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ sourceCompatibility = 1.6 group = 'com.box' archivesBaseName = 'box-java-sdk' -version = '2.2.3-SNAPSHOT' +version = '2.3.0' repositories { mavenCentral() From 09ce2f9e75959294ff12a6f9600bb68ac9decc88 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Tue, 10 Jan 2017 16:04:53 -0800 Subject: [PATCH 038/119] Updating version number in the UserAgent. --- src/main/java/com/box/sdk/BoxAPIConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/box/sdk/BoxAPIConnection.java b/src/main/java/com/box/sdk/BoxAPIConnection.java index ff8b5f4d8..79b472a84 100644 --- a/src/main/java/com/box/sdk/BoxAPIConnection.java +++ b/src/main/java/com/box/sdk/BoxAPIConnection.java @@ -83,7 +83,7 @@ public BoxAPIConnection(String clientID, String clientSecret, String accessToken this.autoRefresh = true; this.maxRequestAttempts = DEFAULT_MAX_ATTEMPTS; this.refreshLock = new ReentrantReadWriteLock(); - this.userAgent = "Box Java SDK v2.2.1-SNAPSHOT"; + this.userAgent = "Box Java SDK v2.3.0"; this.listeners = new ArrayList(); } From 67dd31f2b6f59a53d612fca0563b44495ea7b3a6 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Thu, 9 Feb 2017 18:22:00 -0800 Subject: [PATCH 039/119] Adding event type CONTENT_WORKFLOW_UPLOAD_POLICY_VIOLATION --- src/main/java/com/box/sdk/BoxEvent.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/box/sdk/BoxEvent.java b/src/main/java/com/box/sdk/BoxEvent.java index f46fff8e4..c03332710 100644 --- a/src/main/java/com/box/sdk/BoxEvent.java +++ b/src/main/java/com/box/sdk/BoxEvent.java @@ -604,7 +604,11 @@ public enum Type { /** * Deletion of metadata instance. This is an enterprise-only event. */ - METADATA_INSTANCE_DELETE; + METADATA_INSTANCE_DELETE, + /** + * Content Workflow upload policy violation. This is an enterprise-only event. + */ + CONTENT_WORKFLOW_UPLOAD_POLICY_VIOLATION; } } From 9e5a5ad5c9aa55ad24c8826e8b233d5c8d28136a Mon Sep 17 00:00:00 2001 From: David Maynard Date: Thu, 26 Jan 2017 18:11:52 -0800 Subject: [PATCH 040/119] Some changes to enable getting log output from Integration Tests --- build.gradle | 3 ++- src/test/java/com/box/sdk/TestConfig.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 7f51765e1..d1a4e0018 100644 --- a/build.gradle +++ b/build.gradle @@ -88,9 +88,10 @@ task sourcesJar(type: Jar) { task integrationTest(type: Test) { description 'Runs the integration tests.' group 'Verification' - + testLogging.showStandardStreams = true useJUnit { includeCategories 'com.box.sdk.IntegrationTest' + } } diff --git a/src/test/java/com/box/sdk/TestConfig.java b/src/test/java/com/box/sdk/TestConfig.java index 087a0dc2f..876ff7eed 100644 --- a/src/test/java/com/box/sdk/TestConfig.java +++ b/src/test/java/com/box/sdk/TestConfig.java @@ -25,7 +25,7 @@ final class TestConfig { private TestConfig() { } - public static void setLogLevel(String levelString) { + public static Logger setLogLevel(String levelString) { Level level = Level.parse(levelString); Logger logger = Logger.getLogger("com.box.sdk"); logger.setLevel(level); @@ -43,6 +43,7 @@ public static void setLogLevel(String levelString) { handler.setLevel(level); logger.addHandler(handler); } + return logger; } public static String getAccessToken() { From 0a43984bab801b5a1c2bd3dafc7943c3f852c9e4 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Wed, 8 Feb 2017 18:41:44 -0800 Subject: [PATCH 041/119] Changes to improve Integration Tests --- .gitignore | 3 +++ src/main/java/com/box/sdk/BoxFile.java | 8 ++++---- src/main/java/com/box/sdk/BoxFileVersion.java | 1 - src/test/java/com/box/sdk/BoxCollectionTest.java | 1 + src/test/java/com/box/sdk/BoxFileTest.java | 3 ++- src/test/java/com/box/sdk/BoxTaskTest.java | 1 + src/test/java/com/box/sdk/BoxUserTest.java | 8 ++++++-- src/test/java/com/box/sdk/BoxWebHookTest.java | 11 ++++++----- src/test/java/com/box/sdk/EventStreamTest.java | 10 ++++++++-- src/test/java/com/box/sdk/TestConfig.java | 2 +- 10 files changed, 32 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index 2e1e79263..39827a815 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +gradle/ +gradlew +gradlew.bat .gradle/ gradle.properties build/ diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 48fbd7b68..d9fbc1d1d 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -513,13 +513,13 @@ public void uploadVersion(InputStream fileContent, String fileContentSHA1, Date request.putField("content_modified_at", modified); } - BoxAPIResponse response; + BoxJSONResponse response; if (listener == null) { - response = request.send(); + response = (BoxJSONResponse)request.send(); } else { - response = request.send(listener); + response = (BoxJSONResponse)request.send(listener); } - response.disconnect(); + response.getJSON(); } /** diff --git a/src/main/java/com/box/sdk/BoxFileVersion.java b/src/main/java/com/box/sdk/BoxFileVersion.java index 5e198e075..e5792ea00 100644 --- a/src/main/java/com/box/sdk/BoxFileVersion.java +++ b/src/main/java/com/box/sdk/BoxFileVersion.java @@ -224,7 +224,6 @@ public void promote() { BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); request.setBody(jsonObject.toString()); BoxJSONResponse response = (BoxJSONResponse) request.send(); - response.disconnect(); this.parseJSON(JsonObject.readFrom(response.getJSON())); } } diff --git a/src/test/java/com/box/sdk/BoxCollectionTest.java b/src/test/java/com/box/sdk/BoxCollectionTest.java index 282ac7550..6aea05fab 100644 --- a/src/test/java/com/box/sdk/BoxCollectionTest.java +++ b/src/test/java/com/box/sdk/BoxCollectionTest.java @@ -192,5 +192,6 @@ public void getCollectionItemsSucceeds() { uploadedFile.setCollections(favorites); assertThat(favorites, hasItem(Matchers.hasProperty("ID", equalTo(uploadedFile.getID())))); + uploadedFile.delete(); } } diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index be0158303..27c587892 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -17,6 +17,7 @@ import java.util.Collection; import java.util.Date; import java.util.Iterator; +import java.util.logging.Logger; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; @@ -846,7 +847,7 @@ public void addTaskSucceeds() { byte[] fileBytes = "Non-empty string".getBytes(StandardCharsets.UTF_8); String taskMessage = "Non-empty message"; SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); - Date dueAt = new Date(); + Date dueAt = new Date(new Date().getTime()+1000*24*60*60); InputStream uploadStream = new ByteArrayInputStream(fileBytes); BoxFile uploadedFile = rootFolder.uploadFile(uploadStream, fileName).getResource(); diff --git a/src/test/java/com/box/sdk/BoxTaskTest.java b/src/test/java/com/box/sdk/BoxTaskTest.java index a016b78d6..735c4e8ea 100644 --- a/src/test/java/com/box/sdk/BoxTaskTest.java +++ b/src/test/java/com/box/sdk/BoxTaskTest.java @@ -559,6 +559,7 @@ public void updateInfoSucceeds() { Calendar calendar = new GregorianCalendar(); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); + calendar.add(Calendar.DATE,1); Date dueAt = calendar.getTime(); BoxTask.Info taskInfo = uploadedFile.addTask(BoxTask.Action.REVIEW, originalMessage, dueAt); diff --git a/src/test/java/com/box/sdk/BoxUserTest.java b/src/test/java/com/box/sdk/BoxUserTest.java index 18ed8266a..dc507e50b 100644 --- a/src/test/java/com/box/sdk/BoxUserTest.java +++ b/src/test/java/com/box/sdk/BoxUserTest.java @@ -5,6 +5,7 @@ import java.util.Date; import java.util.Iterator; import java.util.List; +import java.util.logging.Logger; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThanOrEqualTo; @@ -962,7 +963,10 @@ public void getCurrentUserInfoIsCorrect() throws InterruptedException { @Category(IntegrationTest.class) public void createAndDeleteEnterpriseUserSucceeds() { BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); - final String login = "login@box.com"; + // Since deleting users happens in a separate process in the backend + // it is really an asynchronous call. So we have to use a new user in + // this test in case the previous user's deletion hasn't completed. + final String login = "login2@box.com"; final String name = "non-empty name"; BoxUser.Info createdUserInfo = BoxUser.createEnterpriseUser(api, login, name); @@ -998,7 +1002,7 @@ public void getMembershipsHasCorrectMemberships() { @Category(IntegrationTest.class) public void updateInfoSucceeds() { BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); - final String login = "login@box.com"; + final String login = "login3@box.com"; final String originalName = "original name"; final String updatedName = "updated name"; diff --git a/src/test/java/com/box/sdk/BoxWebHookTest.java b/src/test/java/com/box/sdk/BoxWebHookTest.java index 4ec8ee4db..cd009ea24 100644 --- a/src/test/java/com/box/sdk/BoxWebHookTest.java +++ b/src/test/java/com/box/sdk/BoxWebHookTest.java @@ -10,6 +10,7 @@ import java.util.Date; import java.util.HashSet; import java.util.Iterator; +import java.util.logging.Logger; import java.util.Set; import static org.hamcrest.Matchers.equalTo; @@ -463,7 +464,7 @@ public void createWebHookFileSucceeds() throws IOException { BoxFile uploadedFile = rootFolder.uploadFile(uploadStream, fileName).getResource(); try { - URL address = new URL("https://0.0.0.0"); + URL address = new URL("https://www.google.com"); BoxWebHook.Info info = BoxWebHook.create(uploadedFile, address, BoxWebHook.Trigger.FILE_PREVIEWED, BoxWebHook.Trigger.FILE_LOCKED); @@ -489,7 +490,7 @@ public void createWebHookFolderSucceeds() throws IOException { BoxFolder folder = rootFolder.createFolder(folderName).getResource(); try { - URL address = new URL("https://0.0.0.0"); + URL address = new URL("https://www.google.com"); BoxWebHook.Info info = BoxWebHook.create(folder, address, BoxWebHook.Trigger.FOLDER_DOWNLOADED, BoxWebHook.Trigger.FOLDER_COPIED); @@ -518,7 +519,7 @@ public void listWebHooksSucceeds() throws IOException { BoxFile uploadedFile = rootFolder.uploadFile(uploadStream, fileName).getResource(); try { - URL address = new URL("https://0.0.0.0"); + URL address = new URL("https://www.google.com"); BoxWebHook.Info info = BoxWebHook.create(uploadedFile, address, BoxWebHook.Trigger.FILE_PREVIEWED); Iterable webhooks = BoxWebHook.all(api); @@ -545,11 +546,11 @@ public void updateWebHookInfoSucceeds() throws IOException { BoxFile uploadedFile = rootFolder.uploadFile(uploadStream, fileName).getResource(); try { - URL address = new URL("https://0.0.0.0"); + URL address = new URL("https://www.google.com"); BoxWebHook webHook = BoxWebHook.create(uploadedFile, address, BoxWebHook.Trigger.FILE_PREVIEWED, BoxWebHook.Trigger.FILE_LOCKED).getResource(); - URL newAddress = new URL("https://0.0.0.1"); + URL newAddress = new URL("https://www.yahoo.com"); BoxWebHook.Info newInfo = webHook.new Info(); newInfo.setTriggers(BoxWebHook.Trigger.FILE_UNLOCKED); diff --git a/src/test/java/com/box/sdk/EventStreamTest.java b/src/test/java/com/box/sdk/EventStreamTest.java index 74977fb9b..bee17d3d0 100644 --- a/src/test/java/com/box/sdk/EventStreamTest.java +++ b/src/test/java/com/box/sdk/EventStreamTest.java @@ -2,6 +2,7 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @@ -28,8 +29,8 @@ public class EventStreamTest { @Test @Category(IntegrationTest.class) public void receiveEventsForFolderCreateAndFolderDelete() throws InterruptedException { + // Logger logger = TestConfig.setLogLevel("FINE"); final LinkedBlockingQueue observedEvents = new LinkedBlockingQueue(); - BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); EventStream stream = new EventStream(api); stream.addListener(new EventListener() { @@ -60,7 +61,12 @@ public boolean onException(Throwable e) { boolean deletedEventFound = false; while (!createdEventFound || !deletedEventFound) { BoxEvent event = observedEvents.poll(1, TimeUnit.MINUTES); - BoxResource source = event.getSourceInfo().getResource(); + BoxResource.Info sourceInfo = event.getSourceInfo(); + // Some events may not have sourceInfo + if (sourceInfo == null) { + continue; + } + BoxResource source = sourceInfo.getResource(); if (source instanceof BoxFolder) { BoxFolder sourceFolder = (BoxFolder) source; if (sourceFolder.getID().equals(expectedID)) { diff --git a/src/test/java/com/box/sdk/TestConfig.java b/src/test/java/com/box/sdk/TestConfig.java index 876ff7eed..86d3f8fd6 100644 --- a/src/test/java/com/box/sdk/TestConfig.java +++ b/src/test/java/com/box/sdk/TestConfig.java @@ -25,7 +25,7 @@ final class TestConfig { private TestConfig() { } - public static Logger setLogLevel(String levelString) { + public static Logger enableLogger(String levelString) { Level level = Level.parse(levelString); Logger logger = Logger.getLogger("com.box.sdk"); logger.setLevel(level); From cfbb4ed2c07a7b4fd8c54a93ec623593e920eddd Mon Sep 17 00:00:00 2001 From: David Maynard Date: Thu, 9 Feb 2017 17:57:23 -0800 Subject: [PATCH 042/119] Added a new IntegrationTest type for JWT tests --- build.gradle | 12 ++++++++++++ src/main/java/com/box/sdk/BoxFile.java | 4 ++-- src/test/java/com/box/sdk/BoxAPIConnectionTest.java | 4 ++-- src/test/java/com/box/sdk/BoxFileTest.java | 3 +-- src/test/java/com/box/sdk/BoxTaskTest.java | 2 +- .../box/sdk/BoxTransactionalAPIConnectionTest.java | 8 ++++---- src/test/java/com/box/sdk/BoxUserTest.java | 5 ++--- src/test/java/com/box/sdk/BoxWebHookTest.java | 1 - src/test/java/com/box/sdk/EventStreamTest.java | 2 -- src/test/java/com/box/sdk/IntegrationTestJWT.java | 7 +++++++ 10 files changed, 31 insertions(+), 17 deletions(-) create mode 100644 src/test/java/com/box/sdk/IntegrationTestJWT.java diff --git a/build.gradle b/build.gradle index d1a4e0018..c03819a83 100644 --- a/build.gradle +++ b/build.gradle @@ -95,6 +95,17 @@ task integrationTest(type: Test) { } } +task integrationTestJWT(type: Test) { + description 'Runs the JWT based integration tests.' + group 'Verification' + testLogging.showStandardStreams = true + useJUnit { + includeCategories 'com.box.sdk.IntegrationTestJWT' + + } +} + + jacocoTestReport.dependsOn(integrationTest); tasks.withType(JavaCompile) { @@ -125,6 +136,7 @@ artifacts { test { useJUnit { excludeCategories 'com.box.sdk.IntegrationTest' + excludeCategories 'com.box.sdk.IntegrationTestJWT' } } diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index d9fbc1d1d..730b38ad9 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -515,9 +515,9 @@ public void uploadVersion(InputStream fileContent, String fileContentSHA1, Date BoxJSONResponse response; if (listener == null) { - response = (BoxJSONResponse)request.send(); + response = (BoxJSONResponse) request.send(); } else { - response = (BoxJSONResponse)request.send(listener); + response = (BoxJSONResponse) request.send(listener); } response.getJSON(); } diff --git a/src/test/java/com/box/sdk/BoxAPIConnectionTest.java b/src/test/java/com/box/sdk/BoxAPIConnectionTest.java index f7b45d660..c37cd6ce9 100644 --- a/src/test/java/com/box/sdk/BoxAPIConnectionTest.java +++ b/src/test/java/com/box/sdk/BoxAPIConnectionTest.java @@ -210,7 +210,7 @@ public void successfullySavesAndRestoresConnection() { } @Test - @Category(IntegrationTest.class) + @Category(IntegrationTestJWT.class) public void developerEditionAppAuthWorks() { final String enterpriseId = TestConfig.getEnterpriseID(); final String clientId = TestConfig.getClientID(); @@ -253,7 +253,7 @@ public void developerEditionAppAuthWorks() { } @Test - @Category(IntegrationTest.class) + @Category(IntegrationTestJWT.class) public void developerEditionAppUserWorks() { final String enterpriseId = TestConfig.getEnterpriseID(); final String clientId = TestConfig.getClientID(); diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index 27c587892..ca1a1ace3 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -17,7 +17,6 @@ import java.util.Collection; import java.util.Date; import java.util.Iterator; -import java.util.logging.Logger; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; @@ -847,7 +846,7 @@ public void addTaskSucceeds() { byte[] fileBytes = "Non-empty string".getBytes(StandardCharsets.UTF_8); String taskMessage = "Non-empty message"; SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); - Date dueAt = new Date(new Date().getTime()+1000*24*60*60); + Date dueAt = new Date(new Date().getTime() + (1000 * 24 * 60 * 60)); InputStream uploadStream = new ByteArrayInputStream(fileBytes); BoxFile uploadedFile = rootFolder.uploadFile(uploadStream, fileName).getResource(); diff --git a/src/test/java/com/box/sdk/BoxTaskTest.java b/src/test/java/com/box/sdk/BoxTaskTest.java index 735c4e8ea..114ee177a 100644 --- a/src/test/java/com/box/sdk/BoxTaskTest.java +++ b/src/test/java/com/box/sdk/BoxTaskTest.java @@ -559,7 +559,7 @@ public void updateInfoSucceeds() { Calendar calendar = new GregorianCalendar(); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); - calendar.add(Calendar.DATE,1); + calendar.add(Calendar.DATE, 1); Date dueAt = calendar.getTime(); BoxTask.Info taskInfo = uploadedFile.addTask(BoxTask.Action.REVIEW, originalMessage, dueAt); diff --git a/src/test/java/com/box/sdk/BoxTransactionalAPIConnectionTest.java b/src/test/java/com/box/sdk/BoxTransactionalAPIConnectionTest.java index 5bbde4cbe..a6c7d4b5f 100644 --- a/src/test/java/com/box/sdk/BoxTransactionalAPIConnectionTest.java +++ b/src/test/java/com/box/sdk/BoxTransactionalAPIConnectionTest.java @@ -12,7 +12,7 @@ public class BoxTransactionalAPIConnectionTest { @Test - @Category(IntegrationTest.class) + @Category(IntegrationTestJWT.class) public void successfullyCreatesTransactionalConnection() { final String transactionalAccessToken = TestConfig.getTransactionalAccessToken(); @@ -22,7 +22,7 @@ public void successfullyCreatesTransactionalConnection() { } @Test - @Category(IntegrationTest.class) + @Category(IntegrationTestJWT.class) public void successfullyCreatesEmbedLinkWithTransactionalConnection() { final String transactionalAccessToken = TestConfig.getTransactionalAccessToken(); @@ -55,7 +55,7 @@ public void successfullyCreatesEmbedLinkWithTransactionalConnection() { } @Test - @Category(IntegrationTest.class) + @Category(IntegrationTestJWT.class) public void successfullyCreatesEmbedLinkWithResourceScopedTransactionalConnection() { final String transactionalAccessToken = TestConfig.getTransactionalAccessToken(); @@ -83,7 +83,7 @@ public void successfullyCreatesEmbedLinkWithResourceScopedTransactionalConnectio } @Test(expected = BoxAPIException.class) - @Category(IntegrationTest.class) + @Category(IntegrationTestJWT.class) public void throwsWhenAttemptingToCreatesEmbedLinkWithAnotherFilesResourceScopedTransactionalConnection() { final String transactionalAccessToken = TestConfig.getTransactionalAccessToken(); diff --git a/src/test/java/com/box/sdk/BoxUserTest.java b/src/test/java/com/box/sdk/BoxUserTest.java index dc507e50b..d60c0c42e 100644 --- a/src/test/java/com/box/sdk/BoxUserTest.java +++ b/src/test/java/com/box/sdk/BoxUserTest.java @@ -5,7 +5,6 @@ import java.util.Date; import java.util.Iterator; import java.util.List; -import java.util.logging.Logger; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThanOrEqualTo; @@ -966,7 +965,7 @@ public void createAndDeleteEnterpriseUserSucceeds() { // Since deleting users happens in a separate process in the backend // it is really an asynchronous call. So we have to use a new user in // this test in case the previous user's deletion hasn't completed. - final String login = "login2@box.com"; + final String login = "login2@boz.com"; final String name = "non-empty name"; BoxUser.Info createdUserInfo = BoxUser.createEnterpriseUser(api, login, name); @@ -1002,7 +1001,7 @@ public void getMembershipsHasCorrectMemberships() { @Category(IntegrationTest.class) public void updateInfoSucceeds() { BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); - final String login = "login3@box.com"; + final String login = "login3@boz.com"; final String originalName = "original name"; final String updatedName = "updated name"; diff --git a/src/test/java/com/box/sdk/BoxWebHookTest.java b/src/test/java/com/box/sdk/BoxWebHookTest.java index cd009ea24..f04a95bc0 100644 --- a/src/test/java/com/box/sdk/BoxWebHookTest.java +++ b/src/test/java/com/box/sdk/BoxWebHookTest.java @@ -10,7 +10,6 @@ import java.util.Date; import java.util.HashSet; import java.util.Iterator; -import java.util.logging.Logger; import java.util.Set; import static org.hamcrest.Matchers.equalTo; diff --git a/src/test/java/com/box/sdk/EventStreamTest.java b/src/test/java/com/box/sdk/EventStreamTest.java index bee17d3d0..67bf16d2d 100644 --- a/src/test/java/com/box/sdk/EventStreamTest.java +++ b/src/test/java/com/box/sdk/EventStreamTest.java @@ -2,7 +2,6 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; -import java.util.logging.Logger; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @@ -29,7 +28,6 @@ public class EventStreamTest { @Test @Category(IntegrationTest.class) public void receiveEventsForFolderCreateAndFolderDelete() throws InterruptedException { - // Logger logger = TestConfig.setLogLevel("FINE"); final LinkedBlockingQueue observedEvents = new LinkedBlockingQueue(); BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); EventStream stream = new EventStream(api); diff --git a/src/test/java/com/box/sdk/IntegrationTestJWT.java b/src/test/java/com/box/sdk/IntegrationTestJWT.java new file mode 100644 index 000000000..4713c7a9f --- /dev/null +++ b/src/test/java/com/box/sdk/IntegrationTestJWT.java @@ -0,0 +1,7 @@ +package com.box.sdk; + +/** + * Created by dmaynard on 2/9/17. + */ +public interface IntegrationTestJWT { +} From 1e596fb696a459fc7e66ac5cf5abc39bac253e77 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Tue, 21 Feb 2017 15:56:18 -0800 Subject: [PATCH 043/119] Create and update operations are added to Metadata Template --- .../java/com/box/sdk/MetadataTemplate.java | 437 +++++++++++++++++- .../com/box/sdk/MetadataTemplateTest.java | 83 ++++ 2 files changed, 519 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/box/sdk/MetadataTemplate.java b/src/main/java/com/box/sdk/MetadataTemplate.java index 518606d6d..9ca9b3082 100644 --- a/src/main/java/com/box/sdk/MetadataTemplate.java +++ b/src/main/java/com/box/sdk/MetadataTemplate.java @@ -1,12 +1,15 @@ package com.box.sdk; +import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.List; +import com.eclipsesource.json.JsonArray; import com.eclipsesource.json.JsonObject; import com.eclipsesource.json.JsonValue; + /** * The MetadataTemplate class represents the Box metadata template object. * Templates allow the metadata service to provide a multitude of services, @@ -22,6 +25,12 @@ public class MetadataTemplate extends BoxJSONObject { private static final URLTemplate METADATA_TEMPLATE_URL_TEMPLATE = new URLTemplate("metadata_templates/%s/%s/schema"); + /** + * @see #createMetadataTemplate(BoxAPIConnection, String, String, String, boolean, List) + */ + private static final URLTemplate METADATA_TEMPLATE_SCHEMA_URL_TEMPLATE + = new URLTemplate("metadata_templates/schema"); + /** * @see #getEnterpriseMetadataTemplates(String, int, BoxAPIConnection, String...) */ @@ -158,6 +167,201 @@ void parseJSONMember(JsonObject.Member member) { } } + /** + * Creates new metadata template. + * @param api the API connection to be used. + * @param scope the scope of the object. + * @param templateKey a unique identifier for the template. + * @param displayName the display name of the field. + * @param hidden whether this template is hidden in the UI. + * @param fields the ordered set of fields for the template + * @return the metadata template returned from the server. + */ + public static MetadataTemplate createMetadataTemplate(BoxAPIConnection api, String scope, String templateKey, + String displayName, boolean hidden, List fields) { + + JsonObject jsonObject = new JsonObject(); + jsonObject.add("scope", scope); + jsonObject.add("displayName", displayName); + jsonObject.add("hidden", hidden); + + if (templateKey != null) { + jsonObject.add("templateKey", templateKey); + } + + JsonArray fieldsArray = new JsonArray(); + if (fields != null && !fields.isEmpty()) { + for (Field field : fields) { + JsonObject fieldObj = getFieldJsonObject(field); + + fieldsArray.add(fieldObj); + } + + jsonObject.add("fields", fieldsArray); + } + + URL url = METADATA_TEMPLATE_SCHEMA_URL_TEMPLATE.build(api.getBaseURL()); + BoxJSONRequest request = new BoxJSONRequest(api, url, "POST"); + request.setBody(jsonObject.toString()); + System.out.println("Result: " + jsonObject.toString()); + + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); + + return new MetadataTemplate(responseJSON); + } + + /** + * Gets the JsonObject representation of the given field object + * @param field represents a template field + * @return the json object + */ + private static JsonObject getFieldJsonObject(Field field) { + JsonObject fieldObj = new JsonObject(); + fieldObj.add("type", field.getType()); + fieldObj.add("key", field.getKey()); + fieldObj.add("displayName", field.getDisplayName()); + + String fieldDesc = field.getDescription(); + if (fieldDesc != null) { + fieldObj.add("description", field.getDescription()); + } + + Boolean fieldIsHidden = field.getIsHidden(); + if(fieldIsHidden != null) { + fieldObj.add("hidden", field.getIsHidden()); + } + + JsonArray array = new JsonArray(); + List options = field.getOptions(); + if (options != null && !options.isEmpty()) { + for (String option : options) { + JsonObject optionObj = new JsonObject(); + optionObj.add("key", option); + + array.add(optionObj); + } + fieldObj.add("options", array); + } + + return fieldObj; + } + + /** + * Updates the schema of an existing metadata template + * + * @param api the API connection to be used + * @param scope the scope of the object + * @param template Unique identifier of the template + * @param fieldOperations the fields that needs to be updated / added in the template + * @return the updated metadata template + */ + public static MetadataTemplate updateMetadataTemplate(BoxAPIConnection api, String scope, String template, List fieldOperations) { + JsonArray array = new JsonArray(); + + for (FieldOperation fieldOperation : fieldOperations) { + JsonObject jsonObject = getFieldOperationJsonObject(fieldOperation); + array.add(jsonObject); + } + + QueryStringBuilder builder = new QueryStringBuilder(); + URL url = METADATA_TEMPLATE_URL_TEMPLATE.build(api.getBaseURL(), scope, template); + BoxJSONRequest request = new BoxJSONRequest(api, url, "PUT"); + request.setBody(array.toString()); + + System.out.println("Array: " + array.toString()); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject responseJson = JsonObject.readFrom(response.getJSON()); + + return new MetadataTemplate(responseJson); + } + + /** + * Gets the JsonObject representation of the Field Operation + * @param fieldOperation represents the template update operation + * @return the json object + */ + private static JsonObject getFieldOperationJsonObject(FieldOperation fieldOperation) { + JsonObject jsonObject = new JsonObject(); + jsonObject.add("op", fieldOperation.getOp().toString()); + + String fieldKey = fieldOperation.getFieldKey(); + if (fieldKey != null) { + jsonObject.add("fieldKey", fieldKey); + } + + Field field = fieldOperation.getData(); + if (field != null) { + JsonObject fieldObj = new JsonObject(); + + String type = field.getType(); + if(type != null) { + fieldObj.add("type", type); + } + + String key = field.getKey(); + if (key != null) { + fieldObj.add("key", key); + } + + String displayName = field.getDisplayName(); + if(displayName != null) { + fieldObj.add("displayName", displayName); + } + + String description = field.getDescription(); + if (description != null) { + fieldObj.add("description", description); + } + + Boolean hidden = field.getIsHidden(); + if (hidden != null) { + fieldObj.add("hidden", hidden); + } + + List options = field.getOptions(); + if (options != null) { + JsonArray array = new JsonArray(); + for (String option: options) { + JsonObject optionObj = new JsonObject(); + optionObj.add("key", option); + + array.add(optionObj); + } + + fieldObj.add("options", array); + } + + jsonObject.add("data", fieldObj); + } + + List fieldKeys = fieldOperation.getFieldKeys(); + if (fieldKeys != null) { + jsonObject.add("fieldKeys", getJsonArray(fieldKeys)); + } + + List enumOptionKeys = fieldOperation.getEnumOptionKeys(); + if (enumOptionKeys != null) { + jsonObject.add("enumOptionKeys", getJsonArray(enumOptionKeys)); + } + + return jsonObject; + } + + /** + * Gets the Json Array representation of the given list of strings + * @param keys List of strings + * @return the JsonArray represents the list of keys + */ + private static JsonArray getJsonArray(List keys) { + JsonArray array = new JsonArray(); + for (String key : keys) { + array.add(key); + } + + return array; + } + /** * Gets the metadata template of properties. * @param api the API connection to be used. @@ -258,7 +462,7 @@ private static String scopeBasedOnType(String typeName) { /** * Class contains information about the metadata template field. */ - public class Field extends BoxJSONObject { + public static class Field extends BoxJSONObject { /** * @see #getType() @@ -321,6 +525,14 @@ public String getType() { return this.type; } + /** + * Sets the data type of the field's value. + * @param type the data type of the field's value. + */ + public void setType(String type) { + this.type = type; + } + /** * Gets the key of the field. * @return the key of the field. @@ -329,6 +541,14 @@ public String getKey() { return this.key; } + /** + * Sets the key of the field. + * @param key the key of the field. + */ + public void setKey(String key) { + this.key = key; + } + /** * Gets the display name of the field. * @return the display name of the field. @@ -337,6 +557,14 @@ public String getDisplayName() { return this.displayName; } + /** + * Sets the display name of the field. + * @param displayName the display name of the field. + */ + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + /** * Gets is metadata template field hidden. * @return is metadata template field hidden. @@ -345,6 +573,14 @@ public Boolean getIsHidden() { return this.isHidden; } + /** + * Sets is metadata template field hidden. + * @param isHidden is metadata template field hidden? + */ + public void setIsHidden(boolean isHidden) { + this.isHidden = isHidden; + } + /** * Gets the description of the field. * @return the description of the field. @@ -353,6 +589,14 @@ public String getDescription() { return this.description; } + /** + * Sets the description of the field. + * @param description the description of the field. + */ + public void setDescription() { + this.description = description; + } + /** * Gets list of possible options for enum type of the field. * @return list of possible options for enum type of the field. @@ -361,6 +605,14 @@ public List getOptions() { return this.options; } + /** + * Sets list of possible options for enum type of the field. + * @param options list of possible options for enum type of the field. + */ + public void setOptions(List options) { + this.options = options; + } + /** * {@inheritDoc} */ @@ -387,4 +639,187 @@ void parseJSONMember(JsonObject.Member member) { } } + /** + * Posssible operations that can be performed in a Metadata template + * Add an enum option + * Add a field + * Edit a field + * Edit template + * Reorder the enum option + * Reorder the field list + */ + public static class FieldOperation extends BoxJSONObject { + + private Operation op; + private Field data; + private String fieldKey; + private List fieldKeys; + private List enumOptionKeys; + + /** + * Constructs an empty FieldOperation. + */ + public FieldOperation() { + super(); + } + + /** + * Constructs a Field operation from a JSON string. + * @param json the json encoded metadate template field. + */ + public FieldOperation(String json) { + super(json); + } + + /** + * Constructs a Field operation from a JSON object. + * @param jsonObject the json encoded metadate template field. + */ + FieldOperation(JsonObject jsonObject) { + super(jsonObject); + } + + /** + * Gets the operation + * @return the operation + */ + public Operation getOp() { + return op; + } + + /** + * Gets the data associated with the operation + * @return the field object representing the data + */ + public Field getData() { + return data; + } + + /** + * Gets the field key + * @return the field key + */ + public String getFieldKey() { + return fieldKey; + } + + /** + * Gets the list of field keys + * @return the list of Strings + */ + public List getFieldKeys() { + return fieldKeys; + } + + /** + * Gets the list of keys of the Enum options + * @return the list of Strings + */ + public List getEnumOptionKeys() { + return enumOptionKeys; + } + + /** + * Sets the operation + * @param op the operation + */ + public void setOp(Operation op) { + this.op = op; + } + + /** + * Sets the data + * @param data the Field object representing the data + */ + public void setData(Field data) { + this.data = data; + } + + /** + * Sets the field key + * @param fieldKey the key of the field + */ + public void setFieldKey(String fieldKey) { + this.fieldKey = fieldKey; + } + + /** + * Sets the list of the field keys + * @param fieldKeys the list of strings + */ + public void setFieldKeys(List fieldKeys) { + this.fieldKeys = fieldKeys; + } + + /** + * Sets the list of the enum option keys + * @param enumOptionKeys the list of Strings + */ + public void setEnumOptionKeys(List enumOptionKeys) { + this.enumOptionKeys = enumOptionKeys; + } + + /** + * {@inheritDoc} + */ + @Override + void parseJSONMember(JsonObject.Member member) { + JsonValue value = member.getValue(); + String memberName = member.getName(); + if (memberName.equals("op")) { + this.op = Operation.valueOf(value.asString()); + } else if (memberName.equals("data")) { + this.data = new Field(value.asObject()); + } else if (memberName.equals("fieldKey")) { + this.fieldKey = value.asString(); + } else if (memberName.equals("fieldKeys")) { + if(this.fieldKeys == null) { + fieldKeys = new ArrayList(); + } else { + fieldKeys.clear(); + } + + JsonArray array = value.asArray(); + for (JsonValue jsonValue: array) { + fieldKeys.add(jsonValue.asString()); + } + } else if (memberName.equals("enumOptionKeys")) { + if(this.enumOptionKeys == null) { + enumOptionKeys = new ArrayList(); + } else { + enumOptionKeys.clear(); + } + + JsonArray array = value.asArray(); + for (JsonValue jsonValue: array) { + enumOptionKeys.add(jsonValue.asString()); + } + } + } + } + + /** + * Possible template operations + */ + public enum Operation { + + //Adds an enum option at the end of the enum option list for the specified field + addEnumOption, + + //Adds a field at the end of the field list for the template + addField, + + //Edits any number of the base properties of a field: displayName, hidden, description + editField, + + //Edits any number of the base properties of a template: displayName, hidden + editTemplate, + + //Reorders the enum option list to match the requested enum option list + reorderEnumOptions, + + //Reorders the field list to match the requested field list + reorderFields + } + } diff --git a/src/test/java/com/box/sdk/MetadataTemplateTest.java b/src/test/java/com/box/sdk/MetadataTemplateTest.java index 8ca7c60f6..40af0e967 100644 --- a/src/test/java/com/box/sdk/MetadataTemplateTest.java +++ b/src/test/java/com/box/sdk/MetadataTemplateTest.java @@ -1,5 +1,6 @@ package com.box.sdk; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; @@ -249,4 +250,86 @@ public void testGetEnterpriseMetadataTemplatesParseAllFieldsCorrectly() { Assert.assertEquals(secondEntryFieldSecondOption, template.getFields().get(0).getOptions().get(1)); Assert.assertFalse(iterator.hasNext()); } + + @Test + @Category(IntegrationTest.class) + public void createMetadataTemplateSucceeds() { + BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); + + MetadataTemplate.Field ctField = new MetadataTemplate.Field(); + ctField.setType("string"); + ctField.setKey("customerTeam"); + ctField.setDisplayName("Customer Team"); + + MetadataTemplate.Field fyField = new MetadataTemplate.Field(); + fyField.setType("enum"); + fyField.setKey("fy"); + fyField.setDisplayName("FY"); + + List options = new ArrayList(); + options.add("FY16"); + options.add("FY17"); + fyField.setOptions(options); + + List fields = new ArrayList(); + fields.add(ctField); + fields.add(fyField); + + MetadataTemplate template = MetadataTemplate.createMetadataTemplate(api, "enterprise", "documentFlow03", "Document Flow 03", false, fields); + + MetadataTemplate storedTemplate = MetadataTemplate.getMetadataTemplate(api, "documentFlow03"); + Assert.assertNotNull(storedTemplate); + } + + @Test + @Category(IntegrationTest.class) + public void updateMetadataTemplateSucceeds() { + BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); + + List fieldOperations = new ArrayList(); + MetadataTemplate.FieldOperation editField = new MetadataTemplate.FieldOperation(); + editField.setOp(MetadataTemplate.Operation.editField); + editField.setFieldKey("customerTeam"); + + MetadataTemplate.Field customerTeam = new MetadataTemplate.Field(); + customerTeam.setDisplayName("Customer Team modified"); + editField.setData(customerTeam); + fieldOperations.add(editField); + + MetadataTemplate.FieldOperation newField = new MetadataTemplate.FieldOperation(); + newField.setOp(MetadataTemplate.Operation.addField); + + MetadataTemplate.Field deptField = new MetadataTemplate.Field(); + deptField.setType("enum"); + deptField.setKey("department"); + deptField.setDisplayName("Department"); + + List options = new ArrayList(); + options.add("Beauty"); + options.add("Shoes"); + deptField.setOptions(options); + newField.setData(deptField); + + fieldOperations.add(newField); + + MetadataTemplate template = MetadataTemplate.updateMetadataTemplate(api, "enterprise", "documentFlow03", fieldOperations); + Assert.assertNotNull(template); + + MetadataTemplate updatedTemplate = MetadataTemplate.getMetadataTemplate(api, "documentFlow03"); + List fields = updatedTemplate.getFields(); + Assert.assertEquals(4, fields.size()); + + boolean found = false; + for (MetadataTemplate.Field field: fields) { + if ("department".equals(field.getKey())) { + Assert.assertEquals("enum", field.getType()); + Assert.assertEquals("Department", field.getDisplayName()); + Assert.assertEquals(2, field.getOptions().size()); + + found = true; + } + } + + Assert.assertEquals(found, true); + } } From d1f763ef0b64fe75b3dee5716120264de428c578 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Tue, 21 Feb 2017 16:14:41 -0800 Subject: [PATCH 044/119] Javadoc fixes --- .../java/com/box/sdk/MetadataTemplate.java | 108 ++++++++++-------- 1 file changed, 60 insertions(+), 48 deletions(-) diff --git a/src/main/java/com/box/sdk/MetadataTemplate.java b/src/main/java/com/box/sdk/MetadataTemplate.java index 9ca9b3082..713bb9284 100644 --- a/src/main/java/com/box/sdk/MetadataTemplate.java +++ b/src/main/java/com/box/sdk/MetadataTemplate.java @@ -1,6 +1,5 @@ package com.box.sdk; -import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.List; @@ -212,7 +211,7 @@ public static MetadataTemplate createMetadataTemplate(BoxAPIConnection api, Stri } /** - * Gets the JsonObject representation of the given field object + * Gets the JsonObject representation of the given field object. * @param field represents a template field * @return the json object */ @@ -228,7 +227,7 @@ private static JsonObject getFieldJsonObject(Field field) { } Boolean fieldIsHidden = field.getIsHidden(); - if(fieldIsHidden != null) { + if (fieldIsHidden != null) { fieldObj.add("hidden", field.getIsHidden()); } @@ -248,7 +247,7 @@ private static JsonObject getFieldJsonObject(Field field) { } /** - * Updates the schema of an existing metadata template + * Updates the schema of an existing metadata template. * * @param api the API connection to be used * @param scope the scope of the object @@ -256,7 +255,9 @@ private static JsonObject getFieldJsonObject(Field field) { * @param fieldOperations the fields that needs to be updated / added in the template * @return the updated metadata template */ - public static MetadataTemplate updateMetadataTemplate(BoxAPIConnection api, String scope, String template, List fieldOperations) { + public static MetadataTemplate updateMetadataTemplate(BoxAPIConnection api, String scope, String template, + List fieldOperations) { + JsonArray array = new JsonArray(); for (FieldOperation fieldOperation : fieldOperations) { @@ -277,7 +278,7 @@ public static MetadataTemplate updateMetadataTemplate(BoxAPIConnection api, Stri } /** - * Gets the JsonObject representation of the Field Operation + * Gets the JsonObject representation of the Field Operation. * @param fieldOperation represents the template update operation * @return the json object */ @@ -295,7 +296,7 @@ private static JsonObject getFieldOperationJsonObject(FieldOperation fieldOperat JsonObject fieldObj = new JsonObject(); String type = field.getType(); - if(type != null) { + if (type != null) { fieldObj.add("type", type); } @@ -305,7 +306,7 @@ private static JsonObject getFieldOperationJsonObject(FieldOperation fieldOperat } String displayName = field.getDisplayName(); - if(displayName != null) { + if (displayName != null) { fieldObj.add("displayName", displayName); } @@ -349,7 +350,7 @@ private static JsonObject getFieldOperationJsonObject(FieldOperation fieldOperat } /** - * Gets the Json Array representation of the given list of strings + * Gets the Json Array representation of the given list of strings. * @param keys List of strings * @return the JsonArray represents the list of keys */ @@ -593,7 +594,7 @@ public String getDescription() { * Sets the description of the field. * @param description the description of the field. */ - public void setDescription() { + public void setDescription(String description) { this.description = description; } @@ -640,13 +641,13 @@ void parseJSONMember(JsonObject.Member member) { } /** - * Posssible operations that can be performed in a Metadata template - * Add an enum option - * Add a field - * Edit a field - * Edit template - * Reorder the enum option - * Reorder the field list + * Posssible operations that can be performed in a Metadata template. + *
    Add an enum option
+ *
    Add a field
+ *
    Edit a field
+ *
    Edit template
+ *
    Reorder the enum option
+ *
    Reorder the field list
*/ public static class FieldOperation extends BoxJSONObject { @@ -680,47 +681,47 @@ public FieldOperation(String json) { } /** - * Gets the operation + * Gets the operation. * @return the operation */ public Operation getOp() { - return op; + return this.op; } /** - * Gets the data associated with the operation + * Gets the data associated with the operation. * @return the field object representing the data */ public Field getData() { - return data; + return this.data; } /** - * Gets the field key + * Gets the field key. * @return the field key */ public String getFieldKey() { - return fieldKey; + return this.fieldKey; } /** - * Gets the list of field keys + * Gets the list of field keys. * @return the list of Strings */ public List getFieldKeys() { - return fieldKeys; + return this.fieldKeys; } /** - * Gets the list of keys of the Enum options + * Gets the list of keys of the Enum options. * @return the list of Strings */ public List getEnumOptionKeys() { - return enumOptionKeys; + return this.enumOptionKeys; } /** - * Sets the operation + * Sets the operation. * @param op the operation */ public void setOp(Operation op) { @@ -728,7 +729,7 @@ public void setOp(Operation op) { } /** - * Sets the data + * Sets the data. * @param data the Field object representing the data */ public void setData(Field data) { @@ -736,7 +737,7 @@ public void setData(Field data) { } /** - * Sets the field key + * Sets the field key. * @param fieldKey the key of the field */ public void setFieldKey(String fieldKey) { @@ -744,7 +745,7 @@ public void setFieldKey(String fieldKey) { } /** - * Sets the list of the field keys + * Sets the list of the field keys. * @param fieldKeys the list of strings */ public void setFieldKeys(List fieldKeys) { @@ -752,7 +753,7 @@ public void setFieldKeys(List fieldKeys) { } /** - * Sets the list of the enum option keys + * Sets the list of the enum option keys. * @param enumOptionKeys the list of Strings */ public void setEnumOptionKeys(List enumOptionKeys) { @@ -773,53 +774,64 @@ void parseJSONMember(JsonObject.Member member) { } else if (memberName.equals("fieldKey")) { this.fieldKey = value.asString(); } else if (memberName.equals("fieldKeys")) { - if(this.fieldKeys == null) { - fieldKeys = new ArrayList(); + if (this.fieldKeys == null) { + this.fieldKeys = new ArrayList(); } else { - fieldKeys.clear(); + this.fieldKeys.clear(); } JsonArray array = value.asArray(); for (JsonValue jsonValue: array) { - fieldKeys.add(jsonValue.asString()); + this.fieldKeys.add(jsonValue.asString()); } } else if (memberName.equals("enumOptionKeys")) { - if(this.enumOptionKeys == null) { - enumOptionKeys = new ArrayList(); + if (this.enumOptionKeys == null) { + this.enumOptionKeys = new ArrayList(); } else { - enumOptionKeys.clear(); + this.enumOptionKeys.clear(); } JsonArray array = value.asArray(); for (JsonValue jsonValue: array) { - enumOptionKeys.add(jsonValue.asString()); + this.enumOptionKeys.add(jsonValue.asString()); } } } } /** - * Possible template operations + * Possible template operations. */ public enum Operation { - //Adds an enum option at the end of the enum option list for the specified field + /** + * Adds an enum option at the end of the enum option list for the specified field + */ addEnumOption, - //Adds a field at the end of the field list for the template + /** + * Adds a field at the end of the field list for the template + */ addField, - //Edits any number of the base properties of a field: displayName, hidden, description + /** + * Edits any number of the base properties of a field: displayName, hidden, description + */ editField, - //Edits any number of the base properties of a template: displayName, hidden + /** + * Edits any number of the base properties of a template: displayName, hidden + */ editTemplate, - //Reorders the enum option list to match the requested enum option list + /** + * Reorders the enum option list to match the requested enum option list + */ reorderEnumOptions, - //Reorders the field list to match the requested field list + /** + * Reorders the field list to match the requested field list + */ reorderFields } - } From 6f98eb52291edb34ab064875692aad9c47100d22 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Tue, 21 Feb 2017 16:18:07 -0800 Subject: [PATCH 045/119] Javadoc fixes --- src/main/java/com/box/sdk/MetadataTemplate.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/box/sdk/MetadataTemplate.java b/src/main/java/com/box/sdk/MetadataTemplate.java index 713bb9284..88fab8368 100644 --- a/src/main/java/com/box/sdk/MetadataTemplate.java +++ b/src/main/java/com/box/sdk/MetadataTemplate.java @@ -805,32 +805,32 @@ void parseJSONMember(JsonObject.Member member) { public enum Operation { /** - * Adds an enum option at the end of the enum option list for the specified field + * Adds an enum option at the end of the enum option list for the specified field. */ addEnumOption, /** - * Adds a field at the end of the field list for the template + * Adds a field at the end of the field list for the template. */ addField, /** - * Edits any number of the base properties of a field: displayName, hidden, description + * Edits any number of the base properties of a field: displayName, hidden, description. */ editField, /** - * Edits any number of the base properties of a template: displayName, hidden + * Edits any number of the base properties of a template: displayName, hidden. */ editTemplate, /** - * Reorders the enum option list to match the requested enum option list + * Reorders the enum option list to match the requested enum option list. */ reorderEnumOptions, /** - * Reorders the field list to match the requested field list + * Reorders the field list to match the requested field list. */ reorderFields } From cec7b1fb3602da9e455aa355ec8c1266b22020a7 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Tue, 21 Feb 2017 16:20:43 -0800 Subject: [PATCH 046/119] breaking a long line in to two --- src/test/java/com/box/sdk/MetadataTemplateTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/box/sdk/MetadataTemplateTest.java b/src/test/java/com/box/sdk/MetadataTemplateTest.java index 40af0e967..4d42d65ef 100644 --- a/src/test/java/com/box/sdk/MetadataTemplateTest.java +++ b/src/test/java/com/box/sdk/MetadataTemplateTest.java @@ -275,7 +275,8 @@ public void createMetadataTemplateSucceeds() { fields.add(ctField); fields.add(fyField); - MetadataTemplate template = MetadataTemplate.createMetadataTemplate(api, "enterprise", "documentFlow03", "Document Flow 03", false, fields); + MetadataTemplate template = MetadataTemplate.createMetadataTemplate(api, "enterprise", + "documentFlow03", "Document Flow 03", false, fields); MetadataTemplate storedTemplate = MetadataTemplate.getMetadataTemplate(api, "documentFlow03"); Assert.assertNotNull(storedTemplate); From e921f840e5e82f8494ef4d3498d8364a0ead727f Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Tue, 21 Feb 2017 16:22:32 -0800 Subject: [PATCH 047/119] breaking a long line in to two --- src/test/java/com/box/sdk/MetadataTemplateTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/box/sdk/MetadataTemplateTest.java b/src/test/java/com/box/sdk/MetadataTemplateTest.java index 4d42d65ef..d568f9e9f 100644 --- a/src/test/java/com/box/sdk/MetadataTemplateTest.java +++ b/src/test/java/com/box/sdk/MetadataTemplateTest.java @@ -313,7 +313,8 @@ public void updateMetadataTemplateSucceeds() { fieldOperations.add(newField); - MetadataTemplate template = MetadataTemplate.updateMetadataTemplate(api, "enterprise", "documentFlow03", fieldOperations); + MetadataTemplate template = MetadataTemplate.updateMetadataTemplate(api, + "enterprise", "documentFlow03", fieldOperations); Assert.assertNotNull(template); MetadataTemplate updatedTemplate = MetadataTemplate.getMetadataTemplate(api, "documentFlow03"); From 08252d5e7568bc1dc766d3f35c829d2c4e6f08c6 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Wed, 22 Feb 2017 09:27:28 -0800 Subject: [PATCH 048/119] 409 error handling --- src/test/java/com/box/sdk/MetadataTemplateTest.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/box/sdk/MetadataTemplateTest.java b/src/test/java/com/box/sdk/MetadataTemplateTest.java index d568f9e9f..7c5cc9d91 100644 --- a/src/test/java/com/box/sdk/MetadataTemplateTest.java +++ b/src/test/java/com/box/sdk/MetadataTemplateTest.java @@ -275,8 +275,14 @@ public void createMetadataTemplateSucceeds() { fields.add(ctField); fields.add(fyField); - MetadataTemplate template = MetadataTemplate.createMetadataTemplate(api, "enterprise", - "documentFlow03", "Document Flow 03", false, fields); + try { + MetadataTemplate template = MetadataTemplate.createMetadataTemplate(api, "enterprise", + "documentFlow03", "Document Flow 03", false, fields); + } catch (BoxAPIException apiEx) { + //Delete MetadataTemplate is yet to be supported. Due to that template might be existing already. + //This expects the conflict error. To check the MetadataTemplate creation, please replace the id. + Assert.assertEquals(apiEx.getResponseCode(), 409); + } MetadataTemplate storedTemplate = MetadataTemplate.getMetadataTemplate(api, "documentFlow03"); Assert.assertNotNull(storedTemplate); From 3a42fb59456a289e859bc38cda411ece3018a6be Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Wed, 22 Feb 2017 09:29:26 -0800 Subject: [PATCH 049/119] Removing System.out.println --- src/main/java/com/box/sdk/MetadataTemplate.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/box/sdk/MetadataTemplate.java b/src/main/java/com/box/sdk/MetadataTemplate.java index 88fab8368..e7986a92a 100644 --- a/src/main/java/com/box/sdk/MetadataTemplate.java +++ b/src/main/java/com/box/sdk/MetadataTemplate.java @@ -202,7 +202,6 @@ public static MetadataTemplate createMetadataTemplate(BoxAPIConnection api, Stri URL url = METADATA_TEMPLATE_SCHEMA_URL_TEMPLATE.build(api.getBaseURL()); BoxJSONRequest request = new BoxJSONRequest(api, url, "POST"); request.setBody(jsonObject.toString()); - System.out.println("Result: " + jsonObject.toString()); BoxJSONResponse response = (BoxJSONResponse) request.send(); JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); @@ -270,7 +269,6 @@ public static MetadataTemplate updateMetadataTemplate(BoxAPIConnection api, Stri BoxJSONRequest request = new BoxJSONRequest(api, url, "PUT"); request.setBody(array.toString()); - System.out.println("Array: " + array.toString()); BoxJSONResponse response = (BoxJSONResponse) request.send(); JsonObject responseJson = JsonObject.readFrom(response.getJSON()); From 0c63e33966fc0d82913c13cdc3ad63968bc72a69 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Wed, 22 Feb 2017 12:00:38 -0800 Subject: [PATCH 050/119] update metadatatemplate test fix --- src/main/java/com/box/sdk/MetadataTemplate.java | 14 ++++++++------ .../java/com/box/sdk/MetadataTemplateTest.java | 13 +++++++++---- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/box/sdk/MetadataTemplate.java b/src/main/java/com/box/sdk/MetadataTemplate.java index e7986a92a..3895b0526 100644 --- a/src/main/java/com/box/sdk/MetadataTemplate.java +++ b/src/main/java/com/box/sdk/MetadataTemplate.java @@ -640,12 +640,14 @@ void parseJSONMember(JsonObject.Member member) { /** * Posssible operations that can be performed in a Metadata template. - *
    Add an enum option
- *
    Add a field
- *
    Edit a field
- *
    Edit template
- *
    Reorder the enum option
- *
    Reorder the field list
+ *
    + *
  • Add an enum option
  • + *
  • Add a field
  • + *
  • Edit a field
  • + *
  • Edit template
  • + *
  • Reorder the enum option
  • + *
  • Reorder the field list
  • + *
*/ public static class FieldOperation extends BoxJSONObject { diff --git a/src/test/java/com/box/sdk/MetadataTemplateTest.java b/src/test/java/com/box/sdk/MetadataTemplateTest.java index 7c5cc9d91..96e2ffb28 100644 --- a/src/test/java/com/box/sdk/MetadataTemplateTest.java +++ b/src/test/java/com/box/sdk/MetadataTemplateTest.java @@ -319,13 +319,18 @@ public void updateMetadataTemplateSucceeds() { fieldOperations.add(newField); - MetadataTemplate template = MetadataTemplate.updateMetadataTemplate(api, - "enterprise", "documentFlow03", fieldOperations); - Assert.assertNotNull(template); + try { + MetadataTemplate template = MetadataTemplate.updateMetadataTemplate(api, + "enterprise", "documentFlow03", fieldOperations); + Assert.assertNotNull(template); + } catch (BoxAPIException apiEx) { + //Delete MetadataTemplate is yet to be supported. Due to that template might be existing already. + //This 400 invalid request error if the field already exists. + Assert.assertEquals(apiEx.getResponseCode(), 400); + } MetadataTemplate updatedTemplate = MetadataTemplate.getMetadataTemplate(api, "documentFlow03"); List fields = updatedTemplate.getFields(); - Assert.assertEquals(4, fields.size()); boolean found = false; for (MetadataTemplate.Field field: fields) { From e0c228349391d56280f479d5df6511acca483a40 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Thu, 23 Feb 2017 10:42:21 -0800 Subject: [PATCH 051/119] Inital check-in for supercharged file upload create session implementation --- .../java/com/box/sdk/BoxAPIConnection.java | 19 +++ src/main/java/com/box/sdk/BoxFile.java | 47 +++++- .../com/box/sdk/BoxFileUploadSession.java | 146 ++++++++++++++++++ src/main/java/com/box/sdk/BoxFolder.java | 19 +++ src/test/java/com/box/sdk/BoxFileTest.java | 48 ++++++ src/test/java/com/box/sdk/BoxFolderTest.java | 19 +++ 6 files changed, 293 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/box/sdk/BoxFileUploadSession.java diff --git a/src/main/java/com/box/sdk/BoxAPIConnection.java b/src/main/java/com/box/sdk/BoxAPIConnection.java index 79b472a84..0114ad76f 100644 --- a/src/main/java/com/box/sdk/BoxAPIConnection.java +++ b/src/main/java/com/box/sdk/BoxAPIConnection.java @@ -26,6 +26,7 @@ public class BoxAPIConnection { private static final String TOKEN_URL_STRING = "https://api.box.com/oauth2/token"; private static final String DEFAULT_BASE_URL = "https://api.box.com/2.0/"; private static final String DEFAULT_BASE_UPLOAD_URL = "https://upload.box.com/api/2.0/"; + private static final String DEFAULT_BASE_UPLOAD_SESSION_URL = "https://upload.app.box.com/api/2.1/"; /** * The amount of buffer time, in milliseconds, to use when determining if an access token should be refreshed. For @@ -52,6 +53,7 @@ public class BoxAPIConnection { private String tokenURL; private String baseURL; private String baseUploadURL; + private String baseUploadSessionURL; private boolean autoRefresh; private int maxRequestAttempts; private List listeners; @@ -80,6 +82,7 @@ public BoxAPIConnection(String clientID, String clientSecret, String accessToken this.tokenURL = TOKEN_URL_STRING; this.baseURL = DEFAULT_BASE_URL; this.baseUploadURL = DEFAULT_BASE_UPLOAD_URL; + this.baseUploadSessionURL = DEFAULT_BASE_UPLOAD_SESSION_URL; this.autoRefresh = true; this.maxRequestAttempts = DEFAULT_MAX_ATTEMPTS; this.refreshLock = new ReentrantReadWriteLock(); @@ -237,6 +240,22 @@ public void setBaseUploadURL(String baseUploadURL) { this.baseUploadURL = baseUploadURL; } + /** + * Gets the base upload session URL that's used when performing supercharged uploads to Box. + * @return the base upload session URL. + */ + public String getBaseUploadSessionURL() { + return this.baseUploadSessionURL; + } + + /** + * Sets the base upload URL to be used when performing supercharged uploads to Box. + * @param baseUploadSessionURL a base upload URL. + */ + public void setBaseUploadSessionURL(String baseUploadSessionURL) { + this.baseUploadSessionURL = baseUploadSessionURL; + } + /** * Gets the user agent that's used when sending requests to the Box API. * @return the user agent. diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 730b38ad9..46620e70f 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -6,11 +6,7 @@ import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.EnumSet; -import java.util.List; +import java.util.*; import com.eclipsesource.json.JsonArray; import com.eclipsesource.json.JsonObject; @@ -63,6 +59,11 @@ public enum ThumbnailFileType { private static final URLTemplate GET_TASKS_URL_TEMPLATE = new URLTemplate("files/%s/tasks"); private static final URLTemplate GET_THUMBNAIL_PNG_TEMPLATE = new URLTemplate("files/%s/thumbnail.png"); private static final URLTemplate GET_THUMBNAIL_JPG_TEMPLATE = new URLTemplate("files/%s/thumbnail.jpg"); + private static final URLTemplate UPLOAD_SESSION_URL_TEMPLATE = new URLTemplate("files/%s/upload-session"); + private static final URLTemplate UPLOAD_SESSION_STATUS_URL_TEMPLATE = new URLTemplate( + "files/upload-session/%s/status"); + private static final URLTemplate ABORT_UPLOAD_SESSION_URL_TEMPLATE = new URLTemplate("files/upload-session/%s"); + private static final int BUFFER_SIZE = 8192; @@ -856,6 +857,42 @@ public BoxFile.Info setCollections(BoxCollection... collections) { return new Info(jsonObject); } + public BoxFileUploadSession createUploadSession(long fileSize) { + String queryString = new QueryStringBuilder().appendParam("file_size", fileSize).toString(); + URL url = UPLOAD_SESSION_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseUploadSessionURL(), + queryString, this.getID()); + + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); + request.addHeader("Content-Type", "application/json"); + + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + System.out.println("Response: " + jsonObject); + + return new BoxFileUploadSession(jsonObject); + } + + public BoxFileUploadSession getUploadSessionStatus(String sessionId) { + URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), sessionId); + + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET"); + + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + System.out.println("Response: " + jsonObject); + + return new BoxFileUploadSession(jsonObject); + } + + public void abortUploadSession(String sessionId) { + URL url = ABORT_UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), sessionId); + + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "DELETE"); + + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + } + /** * Contains information about a BoxFile. */ diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java new file mode 100644 index 000000000..0deee7543 --- /dev/null +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -0,0 +1,146 @@ +package com.box.sdk; + +import com.eclipsesource.json.JsonObject; +import com.eclipsesource.json.JsonValue; + +import java.net.MalformedURLException; +import java.net.URL; +import java.text.ParseException; +import java.util.Date; +import java.util.Map; + +/** + * + */ +public class BoxFileUploadSession extends BoxJSONObject { + + private Date sessionExpiresAt; + private String uploadSessionId; + private long partSize; + private Endpoints sessionEndpoints; + private int totalParts; + private int partsProcessed; + + public BoxFileUploadSession() { + super(); + } + + public BoxFileUploadSession(String json) { + super(json); + } + + BoxFileUploadSession(JsonObject jsonObject) { + super(jsonObject); + } + + public Date getSessionExpiresAt() { + return sessionExpiresAt; + } + + public String getUploadSessionId() { + return uploadSessionId; + } + + public long getPartSize() { + return partSize; + } + + public Endpoints getSessionEndpoints() { + return sessionEndpoints; + } + + public int getTotalParts() { + return this.totalParts; + } + + public int getPartsProcessed() { + return this.partsProcessed; + } + + @Override + protected void parseJSONMember(JsonObject.Member member) { + + String memberName = member.getName(); + JsonValue value = member.getValue(); + if (memberName.equals("session_expires_at")) { + try { + String dateStr = value.asString(); + this.sessionExpiresAt = BoxDateFormat.parse(dateStr.substring(0, dateStr.length()-1) + "-00:00"); + } catch (ParseException pe) { + assert false : "A ParseException indicates a bug in the SDK."; + } + } else if (memberName.equals("upload_session_id")) { + this.uploadSessionId = value.asString(); + } else if (memberName.equals("part_size")) { + this.partSize = Double.valueOf(value.toString()).longValue(); + } else if (memberName.equals("session_endpoints")) { + this.sessionEndpoints = new Endpoints(value.asObject()); + } else if (memberName.equals("total_parts")) { + this.totalParts = value.asInt(); + } if (memberName.equals("num_parts_processed")) { + this.partsProcessed = value.asInt(); + } + } + + public class Endpoints extends BoxJSONObject { + private URL listPartsEndpoint; + private URL commitEndpoint; + private URL uploadPartEndpoint; + private URL statusEndpoint; + private URL abortEndpoint; + + public Endpoints() { + super(); + } + + public Endpoints(String json) { + super(json); + } + + Endpoints(JsonObject jsonObject) { + super(jsonObject); + } + + public URL getListPartsEndpoint() { + return listPartsEndpoint; + } + + public URL getCommitEndpoint() { + return commitEndpoint; + } + + public URL getUploadPartEndpoint() { + return uploadPartEndpoint; + } + + public URL getStatusEndpoint() { + return statusEndpoint; + } + + public URL getAbortEndpoint() { + return abortEndpoint; + } + + @Override + protected void parseJSONMember(JsonObject.Member member) { + + String memberName = member.getName(); + JsonValue value = member.getValue(); + try { + if (memberName.equals("list_parts")) { + this.listPartsEndpoint = new URL(value.asString()); + } else if (memberName.equals("commit")) { + this.commitEndpoint = new URL(value.asString()); + } else if (memberName.equals("upload_part")) { + this.uploadPartEndpoint = new URL(value.asString()); + } else if (memberName.equals("status")) { + this.statusEndpoint = new URL(value.asString()); + } else if (memberName.equals("abort")) { + this.abortEndpoint = new URL(value.asString()); + } + } catch(MalformedURLException mue) { + assert false : "A ParseException indicates a bug in the SDK."; + } + } + } +} diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index d07ee7039..13d6f0cc1 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -42,6 +42,7 @@ public class BoxFolder extends BoxItem implements Iterable { private static final URLTemplate GET_ITEMS_URL = new URLTemplate("folders/%s/items/"); private static final URLTemplate SEARCH_URL_TEMPLATE = new URLTemplate("search"); private static final URLTemplate METADATA_URL_TEMPLATE = new URLTemplate("folders/%s/metadata/%s/%s"); + private static final URLTemplate UPLOAD_SESSION_URL_TEMPLATE = new URLTemplate("files/upload-session"); /** * Constructs a BoxFolder for a folder with a given ID. @@ -728,6 +729,24 @@ public void deleteMetadata(String templateName, String scope) { response.disconnect(); } + public BoxFileUploadSession createUploadSession(String folderId, long fileSize, String fileName) { + + String queryString = new QueryStringBuilder() + .appendParam("folder_id", folderId) + .appendParam("file_size", fileSize) + .appendParam("file_name", fileName) + .toString(); + + URL url = UPLOAD_SESSION_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseUploadSessionURL(), + queryString); + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + System.out.println("Response: " + jsonObject); + + return new BoxFileUploadSession(jsonObject); + } + /** * Contains information about a BoxFolder. */ diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index ca1a1ace3..2a87411e6 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -966,6 +966,54 @@ public void setCollectionsWithInfoSucceeds() { uploadedFile.delete(); } + @Test + @Category(IntegrationTest.class) + public void uploadSessionSucceeds() { + BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); + BoxFolder rootFolder = BoxFolder.getRootFolder(api); + String fileName = "[createUploadSessionSucceeds] Test File.txt"; + String fileContent = "Test file"; + byte[] fileBytes = fileContent.getBytes(StandardCharsets.UTF_8); + + InputStream uploadStream = new ByteArrayInputStream(fileBytes); + BoxFile uploadedFile = rootFolder.uploadFile(uploadStream, fileName).getResource(); + try { + BoxFileUploadSession session = uploadedFile.createUploadSession(10000000); + Assert.assertNotNull(session.getUploadSessionId()); + Assert.assertNotNull(session.getSessionExpiresAt()); + Assert.assertNotNull(session.getPartSize()); + + BoxFileUploadSession.Endpoints endpoints = session.getSessionEndpoints(); + Assert.assertNotNull(endpoints); + Assert.assertNotNull(endpoints.getUploadPartEndpoint()); + Assert.assertNotNull(endpoints.getStatusEndpoint()); + Assert.assertNotNull(endpoints.getListPartsEndpoint()); + Assert.assertNotNull(endpoints.getCommitEndpoint()); + Assert.assertNotNull(endpoints.getAbortEndpoint()); + + //Verify the status of the session + getUploadSessionStatusSucceeds(uploadedFile, session.getUploadSessionId()); + + //Verify the delete session + abortUploadSessionStatusSucceeds(uploadedFile, session.getUploadSessionId()); + } finally { + uploadedFile.delete(); + } + } + + private void getUploadSessionStatusSucceeds(BoxFile file, String sessionId) { + BoxFileUploadSession session = file.getUploadSessionStatus(sessionId); + Assert.assertNotNull(session.getSessionExpiresAt()); + Assert.assertNotNull(session.getPartSize()); + Assert.assertNotNull(session.getTotalParts()); + Assert.assertNotNull(session.getPartsProcessed()); + } + + private void abortUploadSessionStatusSucceeds(BoxFile file, String sessionId) { + file.abortUploadSession(sessionId); + + } + private static byte[] readAllBytes(String fileName) throws IOException { RandomAccessFile f = new RandomAccessFile(fileName, "r"); byte[] b = new byte[(int) f.length()]; diff --git a/src/test/java/com/box/sdk/BoxFolderTest.java b/src/test/java/com/box/sdk/BoxFolderTest.java index fdc7d75d3..c24f22642 100644 --- a/src/test/java/com/box/sdk/BoxFolderTest.java +++ b/src/test/java/com/box/sdk/BoxFolderTest.java @@ -1065,4 +1065,23 @@ public void sharedLinkInfoHasEffectiveAccess() { folder.delete(true); } + + @Test + @Category(IntegrationTest.class) + public void createUploadSessionSucceeds() { + BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); + BoxFolder rootFolder = BoxFolder.getRootFolder(api); + BoxFileUploadSession session = rootFolder.createUploadSession("0", 1000000, "Test_File.txt"); + Assert.assertNotNull(session.getUploadSessionId()); + Assert.assertNotNull(session.getSessionExpiresAt()); + Assert.assertNotNull(session.getPartSize()); + + BoxFileUploadSession.Endpoints endpoints = session.getSessionEndpoints(); + Assert.assertNotNull(endpoints); + Assert.assertNotNull(endpoints.getUploadPartEndpoint()); + Assert.assertNotNull(endpoints.getStatusEndpoint()); + Assert.assertNotNull(endpoints.getListPartsEndpoint()); + Assert.assertNotNull(endpoints.getCommitEndpoint()); + Assert.assertNotNull(endpoints.getAbortEndpoint()); + } } From 147345ca71b8f39345fa6329d8ce950cece2170b Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Thu, 23 Feb 2017 11:14:29 -0800 Subject: [PATCH 052/119] Abort session implementation --- src/main/java/com/box/sdk/BoxFile.java | 6 +++--- src/test/java/com/box/sdk/BoxFileTest.java | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 46620e70f..835b52414 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -873,7 +873,7 @@ public BoxFileUploadSession createUploadSession(long fileSize) { } public BoxFileUploadSession getUploadSessionStatus(String sessionId) { - URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), sessionId); + URL url = UPLOAD_SESSION_STATUS_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), sessionId); BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET"); @@ -889,8 +889,8 @@ public void abortUploadSession(String sessionId) { BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "DELETE"); - BoxJSONResponse response = (BoxJSONResponse) request.send(); - JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + BoxAPIResponse response = (BoxAPIResponse) request.send(); + System.out.println("Abort session status: " + response.getResponseCode()); } /** diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index 2a87411e6..19ad7a168 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -1012,6 +1012,14 @@ private void getUploadSessionStatusSucceeds(BoxFile file, String sessionId) { private void abortUploadSessionStatusSucceeds(BoxFile file, String sessionId) { file.abortUploadSession(sessionId); + try { + BoxFileUploadSession session = file.getUploadSessionStatus(sessionId); + + //If the session is aborted, this line should not be executed. + Assert.assertFalse("Upload session is not deleted", true); + } catch(BoxAPIException apiEx) { + Assert.assertEquals(apiEx.getResponseCode(), 404); + } } private static byte[] readAllBytes(String fileName) throws IOException { From 054cb0118d95d8ab9e168324b0bab14d601bc807 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Thu, 23 Feb 2017 14:50:51 -0800 Subject: [PATCH 053/119] Added getAllMetadata Integration Test Upped version to 2.3.1-SNAPSHOT --- build.gradle | 2 +- .../java/com/box/sdk/BoxAPIConnection.java | 2 +- .../com/box/sdk/MetadataTemplateTest.java | 78 +++++++++++++++++++ 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index c03819a83..b18bfdb48 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ sourceCompatibility = 1.6 group = 'com.box' archivesBaseName = 'box-java-sdk' -version = '2.3.0' +version = '2.3.1-SNAPSHOT' repositories { mavenCentral() diff --git a/src/main/java/com/box/sdk/BoxAPIConnection.java b/src/main/java/com/box/sdk/BoxAPIConnection.java index 79b472a84..84e7bdf68 100644 --- a/src/main/java/com/box/sdk/BoxAPIConnection.java +++ b/src/main/java/com/box/sdk/BoxAPIConnection.java @@ -83,7 +83,7 @@ public BoxAPIConnection(String clientID, String clientSecret, String accessToken this.autoRefresh = true; this.maxRequestAttempts = DEFAULT_MAX_ATTEMPTS; this.refreshLock = new ReentrantReadWriteLock(); - this.userAgent = "Box Java SDK v2.3.0"; + this.userAgent = "Box Java SDK v2.3.1"; this.listeners = new ArrayList(); } diff --git a/src/test/java/com/box/sdk/MetadataTemplateTest.java b/src/test/java/com/box/sdk/MetadataTemplateTest.java index 96e2ffb28..4991d90b0 100644 --- a/src/test/java/com/box/sdk/MetadataTemplateTest.java +++ b/src/test/java/com/box/sdk/MetadataTemplateTest.java @@ -1,5 +1,7 @@ package com.box.sdk; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -282,6 +284,7 @@ public void createMetadataTemplateSucceeds() { //Delete MetadataTemplate is yet to be supported. Due to that template might be existing already. //This expects the conflict error. To check the MetadataTemplate creation, please replace the id. Assert.assertEquals(apiEx.getResponseCode(), 409); + Assert.assertTrue(apiEx.getResponse().contains("Template key already exists in this scope")); } MetadataTemplate storedTemplate = MetadataTemplate.getMetadataTemplate(api, "documentFlow03"); @@ -345,4 +348,79 @@ public void updateMetadataTemplateSucceeds() { Assert.assertEquals(found, true); } + + @Test + @Category(IntegrationTest.class) + public void getAllMetadataSucceeds() { + BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); + BoxFolder rootFolder = BoxFolder.getRootFolder(api); + String fileName = "[getAllMetadataSucceeds] Test File.txt"; + byte[] fileBytes = "Non-empty string".getBytes(StandardCharsets.UTF_8); + + InputStream uploadStream = new ByteArrayInputStream(fileBytes); + BoxFile uploadedFile = rootFolder.uploadFile(uploadStream, fileName).getResource(); + + uploadedFile.createMetadata(new Metadata().add("/firstName", "John").add("/lastName", "Smith")); + Metadata check1 = uploadedFile.getMetadata(); + Assert.assertNotNull(check1); + Assert.assertEquals("John", check1.get("/firstName")); + Assert.assertEquals("Smith", check1.get("/lastName")); + + MetadataTemplate.Field ctField = new MetadataTemplate.Field(); + ctField.setType("string"); + ctField.setKey("customerTeam"); + ctField.setDisplayName("Customer Team"); + + MetadataTemplate.Field fyField = new MetadataTemplate.Field(); + fyField.setType("enum"); + fyField.setKey("fy"); + fyField.setDisplayName("FY"); + + List options = new ArrayList(); + options.add("FY16"); + options.add("FY17"); + fyField.setOptions(options); + + List fields = new ArrayList(); + fields.add(ctField); + fields.add(fyField); + + try { + MetadataTemplate template = MetadataTemplate.createMetadataTemplate(api, "enterprise", + "documentFlow03", "Document Flow 03", false, fields); + } catch (BoxAPIException apiEx) { + //Delete MetadataTemplate is yet to be supported. Due to that template might be existing already. + //This expects the conflict error. + Assert.assertEquals(apiEx.getResponseCode(), 409); + Assert.assertTrue(apiEx.getResponse().contains("Template key already exists in this scope")); + } + + MetadataTemplate storedTemplate = MetadataTemplate.getMetadataTemplate(api, "documentFlow03"); + Assert.assertNotNull(storedTemplate); + + Metadata customerMetaData = new Metadata(); + customerMetaData.add("/customerTeam", "MyTeam"); + customerMetaData.add("/fy", "FY17"); + + uploadedFile.createMetadata("documentFlow03", "enterprise", customerMetaData); + + Iterable allMetadata = uploadedFile.getAllMetadata("/firstName", "/lastName"); + Assert.assertNotNull(allMetadata); + Iterator iter = allMetadata.iterator(); + int numTemplates = 0; + while (iter.hasNext()) { + Metadata metadata = iter.next(); + numTemplates++; + if (metadata.getTemplateName().equals("properties")) { + Assert.assertEquals(metadata.get("/firstName"), "John"); + Assert.assertEquals(metadata.get("/lastName"), "Smith"); + } + if (metadata.getTemplateName().equals("documentFlow03")) { + Assert.assertEquals(metadata.get("/customerTeam"), "MyTeam"); + Assert.assertEquals(metadata.get("/fy"), "FY17"); + } + } + Assert.assertEquals(numTemplates, 2); + uploadedFile.delete(); + } } From 3493c8c030f1a11844e503d109f9187ceb25fadb Mon Sep 17 00:00:00 2001 From: David Maynard Date: Thu, 23 Feb 2017 15:36:54 -0800 Subject: [PATCH 054/119] Updated version number to 2.4.0 since we have a new createMetadata endpoint --- build.gradle | 2 +- src/main/java/com/box/sdk/BoxAPIConnection.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index b18bfdb48..76c9fde6d 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ sourceCompatibility = 1.6 group = 'com.box' archivesBaseName = 'box-java-sdk' -version = '2.3.1-SNAPSHOT' +version = '2.4.0-SNAPSHOT' repositories { mavenCentral() diff --git a/src/main/java/com/box/sdk/BoxAPIConnection.java b/src/main/java/com/box/sdk/BoxAPIConnection.java index 84e7bdf68..2c54aeb6e 100644 --- a/src/main/java/com/box/sdk/BoxAPIConnection.java +++ b/src/main/java/com/box/sdk/BoxAPIConnection.java @@ -83,7 +83,7 @@ public BoxAPIConnection(String clientID, String clientSecret, String accessToken this.autoRefresh = true; this.maxRequestAttempts = DEFAULT_MAX_ATTEMPTS; this.refreshLock = new ReentrantReadWriteLock(); - this.userAgent = "Box Java SDK v2.3.1"; + this.userAgent = "Box Java SDK v2.4.0"; this.listeners = new ArrayList(); } From 17510414a2ef3ae6772b5dd81d013cdde6f46481 Mon Sep 17 00:00:00 2001 From: Harish Gokavarapu Date: Thu, 23 Feb 2017 17:32:48 -0800 Subject: [PATCH 055/119] First attempt at generalising FileUploadSession --- src/main/java/com/box/sdk/BoxAPIRequest.java | 6 + src/main/java/com/box/sdk/BoxFile.java | 23 +--- .../com/box/sdk/BoxFileUploadSession.java | 108 +++++++++++++----- src/main/java/com/box/sdk/BoxFolder.java | 2 +- .../java/com/box/sdk/http/ContentType.java | 8 ++ .../java/com/box/sdk/http/HttpHeaders.java | 12 ++ .../java/com/box/sdk/http/HttpMethod.java | 8 ++ 7 files changed, 113 insertions(+), 54 deletions(-) create mode 100644 src/main/java/com/box/sdk/http/ContentType.java create mode 100644 src/main/java/com/box/sdk/http/HttpHeaders.java create mode 100644 src/main/java/com/box/sdk/http/HttpMethod.java diff --git a/src/main/java/com/box/sdk/BoxAPIRequest.java b/src/main/java/com/box/sdk/BoxAPIRequest.java index 11e779cfa..5e15bd372 100644 --- a/src/main/java/com/box/sdk/BoxAPIRequest.java +++ b/src/main/java/com/box/sdk/BoxAPIRequest.java @@ -1,5 +1,7 @@ package com.box.sdk; +import com.box.sdk.http.HttpMethod; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -76,6 +78,10 @@ public BoxAPIRequest(BoxAPIConnection api, URL url, String method) { this.addHeader("Accept-Charset", "utf-8"); } + public BoxAPIRequest(BoxAPIConnection api, URL uploadPartEndpoint, HttpMethod post) { + this(api, uploadPartEndpoint, post.name()); + } + /** * Adds an HTTP header to this request. * @param key the header key. diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 835b52414..0eda0637c 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -869,28 +869,7 @@ public BoxFileUploadSession createUploadSession(long fileSize) { JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); System.out.println("Response: " + jsonObject); - return new BoxFileUploadSession(jsonObject); - } - - public BoxFileUploadSession getUploadSessionStatus(String sessionId) { - URL url = UPLOAD_SESSION_STATUS_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), sessionId); - - BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET"); - - BoxJSONResponse response = (BoxJSONResponse) request.send(); - JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); - System.out.println("Response: " + jsonObject); - - return new BoxFileUploadSession(jsonObject); - } - - public void abortUploadSession(String sessionId) { - URL url = ABORT_UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), sessionId); - - BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "DELETE"); - - BoxAPIResponse response = (BoxAPIResponse) request.send(); - System.out.println("Abort session status: " + response.getResponseCode()); + return new BoxFileUploadSession(this.getAPI(), jsonObject); } /** diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index 0deee7543..fc8032f71 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -1,36 +1,51 @@ package com.box.sdk; +import com.box.sdk.http.ContentType; +import com.box.sdk.http.HttpHeaders; +import com.box.sdk.http.HttpMethod; import com.eclipsesource.json.JsonObject; import com.eclipsesource.json.JsonValue; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.text.ParseException; import java.util.Date; -import java.util.Map; /** * */ public class BoxFileUploadSession extends BoxJSONObject { + private BoxAPIConnection api; private Date sessionExpiresAt; private String uploadSessionId; - private long partSize; private Endpoints sessionEndpoints; + private long partSize; private int totalParts; private int partsProcessed; - public BoxFileUploadSession() { - super(); + private BoxAPIConnection getAPI() { + return api; } - public BoxFileUploadSession(String json) { - super(json); + private void setAPI(BoxAPIConnection api) { + this.api = api; } - BoxFileUploadSession(JsonObject jsonObject) { - super(jsonObject); + public BoxFileUploadSession getResource() { + return BoxFileUploadSession.this; + } + + public int getTotalParts() { + return this.totalParts; + } + + public int getPartsProcessed() { + return this.partsProcessed; } public Date getSessionExpiresAt() { @@ -41,31 +56,27 @@ public String getUploadSessionId() { return uploadSessionId; } - public long getPartSize() { - return partSize; - } - public Endpoints getSessionEndpoints() { return sessionEndpoints; } - public int getTotalParts() { - return this.totalParts; + public long getPartSize() { + return partSize; } - public int getPartsProcessed() { - return this.partsProcessed; + public BoxFileUploadSession(BoxAPIConnection api, JsonObject jsonObject) { + super(jsonObject); + this.api = api; } - @Override protected void parseJSONMember(JsonObject.Member member) { String memberName = member.getName(); JsonValue value = member.getValue(); if (memberName.equals("session_expires_at")) { - try { + try { String dateStr = value.asString(); - this.sessionExpiresAt = BoxDateFormat.parse(dateStr.substring(0, dateStr.length()-1) + "-00:00"); + this.sessionExpiresAt = BoxDateFormat.parse(dateStr.substring(0, dateStr.length()-1) + "-00:00"); } catch (ParseException pe) { assert false : "A ParseException indicates a bug in the SDK."; } @@ -83,23 +94,23 @@ protected void parseJSONMember(JsonObject.Member member) { } public class Endpoints extends BoxJSONObject { - private URL listPartsEndpoint; - private URL commitEndpoint; - private URL uploadPartEndpoint; - private URL statusEndpoint; - private URL abortEndpoint; + private URL listPartsEndpoint; + private URL commitEndpoint; + private URL uploadPartEndpoint; + private URL statusEndpoint; + private URL abortEndpoint; public Endpoints() { - super(); - } + super(); + } - public Endpoints(String json) { + public Endpoints(String json) { super(json); - } + } - Endpoints(JsonObject jsonObject) { + Endpoints(JsonObject jsonObject) { super(jsonObject); - } + } public URL getListPartsEndpoint() { return listPartsEndpoint; @@ -128,7 +139,7 @@ protected void parseJSONMember(JsonObject.Member member) { JsonValue value = member.getValue(); try { if (memberName.equals("list_parts")) { - this.listPartsEndpoint = new URL(value.asString()); + this.listPartsEndpoint = new URL(value.asString()); } else if (memberName.equals("commit")) { this.commitEndpoint = new URL(value.asString()); } else if (memberName.equals("upload_part")) { @@ -143,4 +154,39 @@ protected void parseJSONMember(JsonObject.Member member) { } } } + + /** + * + */ + public void uploadPart(String xBoxPartId, byte[] bytes, long offset, Long partSize, long totalSizeOfFile, String contentRangeUnits) throws MalformedURLException, NoSuchAlgorithmException { + BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), this.getSessionEndpoints().getUploadPartEndpoint(), HttpMethod.POST); + request.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_OCTET_STREAM); + request.addHeader(HttpHeaders.X_BOX_PART_ID, xBoxPartId); + byte[] digestBytes = MessageDigest.getInstance("SHA1").digest(bytes); + String digest = Base64.encode(digestBytes); + request.addHeader("Digest", "sha=" + digest); + if(null == contentRangeUnits){ + contentRangeUnits = "bytes"; + } + request.addHeader(HttpHeaders.CONTENT_RANGE, contentRangeUnits+" "+offset+"-"+(offset+partSize-1)+"/"+totalSizeOfFile); + request.addHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(bytes.length)); + request.setBody(new ByteArrayInputStream(bytes)); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + } + + public BoxFileUploadSession getUploadSessionStatus(String sessionId) { + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), this.getSessionEndpoints().getStatusEndpoint(), "GET"); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + System.out.println("Response: " + jsonObject); + + return new BoxFileUploadSession(this.getAPI(), jsonObject); + } + + + public void abortUploadSession(String sessionId) { + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), this.getSessionEndpoints().getAbortEndpoint(), "DELETE"); + BoxAPIResponse response = (BoxAPIResponse) request.send(); + System.out.println("Abort session status: " + response.getResponseCode()); + } } diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index 13d6f0cc1..c6df62074 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -744,7 +744,7 @@ public BoxFileUploadSession createUploadSession(String folderId, long fileSize, JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); System.out.println("Response: " + jsonObject); - return new BoxFileUploadSession(jsonObject); + return new BoxFileUploadSession(this.getAPI(), jsonObject); } /** diff --git a/src/main/java/com/box/sdk/http/ContentType.java b/src/main/java/com/box/sdk/http/ContentType.java new file mode 100644 index 000000000..4f6b4ebb4 --- /dev/null +++ b/src/main/java/com/box/sdk/http/ContentType.java @@ -0,0 +1,8 @@ +package com.box.sdk.http; + +/** + * + */ +public class ContentType { + public static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; +} diff --git a/src/main/java/com/box/sdk/http/HttpHeaders.java b/src/main/java/com/box/sdk/http/HttpHeaders.java new file mode 100644 index 000000000..fa9b12404 --- /dev/null +++ b/src/main/java/com/box/sdk/http/HttpHeaders.java @@ -0,0 +1,12 @@ +package com.box.sdk.http; + +/** + * + */ +public class HttpHeaders { + public static final String CONTENT_TYPE = "Content-Type"; + public static final String CONTENT_LENGTH = "Content-Length"; + public static final String CONTENT_RANGE = "Content-Range"; + public static final String DIGEST = "Digest"; + public static final String X_BOX_PART_ID = "X-Box-Part-Id"; +} diff --git a/src/main/java/com/box/sdk/http/HttpMethod.java b/src/main/java/com/box/sdk/http/HttpMethod.java new file mode 100644 index 000000000..4121191b5 --- /dev/null +++ b/src/main/java/com/box/sdk/http/HttpMethod.java @@ -0,0 +1,8 @@ +package com.box.sdk.http; + +/** + * + */ +public enum HttpMethod { + GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; +} From 7dcb943bf789e7af96854e25ea166db0c6f9160c Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Sun, 26 Feb 2017 19:20:42 -0800 Subject: [PATCH 056/119] Upload implementation changes & code refactoring to match existing code style --- src/main/java/com/box/sdk/BoxAPIRequest.java | 8 +- src/main/java/com/box/sdk/BoxFile.java | 12 +- .../com/box/sdk/BoxFileUploadSession.java | 436 +++++++++++------- src/main/java/com/box/sdk/BoxFolder.java | 7 +- src/main/java/com/box/sdk/BoxJSONRequest.java | 14 + src/main/java/com/box/sdk/BoxResource.java | 2 + .../java/com/box/sdk/http/ContentType.java | 10 +- .../java/com/box/sdk/http/HttpHeaders.java | 17 +- .../java/com/box/sdk/http/HttpMethod.java | 3 +- 9 files changed, 314 insertions(+), 195 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxAPIRequest.java b/src/main/java/com/box/sdk/BoxAPIRequest.java index 5e15bd372..de7b3585a 100644 --- a/src/main/java/com/box/sdk/BoxAPIRequest.java +++ b/src/main/java/com/box/sdk/BoxAPIRequest.java @@ -1,7 +1,5 @@ package com.box.sdk; -import com.box.sdk.http.HttpMethod; - import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -16,6 +14,8 @@ import java.util.logging.Level; import java.util.logging.Logger; +import com.box.sdk.http.HttpMethod; + /** * Used to make HTTP requests to the Box API. * @@ -78,8 +78,8 @@ public BoxAPIRequest(BoxAPIConnection api, URL url, String method) { this.addHeader("Accept-Charset", "utf-8"); } - public BoxAPIRequest(BoxAPIConnection api, URL uploadPartEndpoint, HttpMethod post) { - this(api, uploadPartEndpoint, post.name()); + public BoxAPIRequest(BoxAPIConnection api, URL uploadPartEndpoint, HttpMethod method) { + this(api, uploadPartEndpoint, method.name()); } /** diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 0eda0637c..bda7859e5 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -6,7 +6,11 @@ import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.EnumSet; +import java.util.List; import com.eclipsesource.json.JsonArray; import com.eclipsesource.json.JsonObject; @@ -857,7 +861,7 @@ public BoxFile.Info setCollections(BoxCollection... collections) { return new Info(jsonObject); } - public BoxFileUploadSession createUploadSession(long fileSize) { + public BoxFileUploadSession.Info createUploadSession(long fileSize) { String queryString = new QueryStringBuilder().appendParam("file_size", fileSize).toString(); URL url = UPLOAD_SESSION_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseUploadSessionURL(), queryString, this.getID()); @@ -869,7 +873,9 @@ public BoxFileUploadSession createUploadSession(long fileSize) { JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); System.out.println("Response: " + jsonObject); - return new BoxFileUploadSession(this.getAPI(), jsonObject); + String sessionId = jsonObject.get("upload_session_id").asString(); + BoxFileUploadSession session = new BoxFileUploadSession(this.getAPI(), sessionId); + return session.new Info(jsonObject); } /** diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index fc8032f71..7cae4c53f 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -1,11 +1,5 @@ package com.box.sdk; -import com.box.sdk.http.ContentType; -import com.box.sdk.http.HttpHeaders; -import com.box.sdk.http.HttpMethod; -import com.eclipsesource.json.JsonObject; -import com.eclipsesource.json.JsonValue; - import java.io.ByteArrayInputStream; import java.io.InputStream; import java.net.MalformedURLException; @@ -15,178 +9,266 @@ import java.text.ParseException; import java.util.Date; +import com.box.sdk.http.ContentType; +import com.box.sdk.http.HttpHeaders; +import com.box.sdk.http.HttpMethod; +import com.eclipsesource.json.JsonObject; +import com.eclipsesource.json.JsonValue; + /** * */ -public class BoxFileUploadSession extends BoxJSONObject { - - private BoxAPIConnection api; - private Date sessionExpiresAt; - private String uploadSessionId; - private Endpoints sessionEndpoints; - private long partSize; - private int totalParts; - private int partsProcessed; - - private BoxAPIConnection getAPI() { - return api; - } - - private void setAPI(BoxAPIConnection api) { - this.api = api; - } - - public BoxFileUploadSession getResource() { - return BoxFileUploadSession.this; - } - - public int getTotalParts() { - return this.totalParts; - } - - public int getPartsProcessed() { - return this.partsProcessed; - } - - public Date getSessionExpiresAt() { - return sessionExpiresAt; - } - - public String getUploadSessionId() { - return uploadSessionId; - } - - public Endpoints getSessionEndpoints() { - return sessionEndpoints; - } - - public long getPartSize() { - return partSize; - } - - public BoxFileUploadSession(BoxAPIConnection api, JsonObject jsonObject) { - super(jsonObject); - this.api = api; - } - - protected void parseJSONMember(JsonObject.Member member) { - - String memberName = member.getName(); - JsonValue value = member.getValue(); - if (memberName.equals("session_expires_at")) { - try { - String dateStr = value.asString(); - this.sessionExpiresAt = BoxDateFormat.parse(dateStr.substring(0, dateStr.length()-1) + "-00:00"); - } catch (ParseException pe) { - assert false : "A ParseException indicates a bug in the SDK."; - } - } else if (memberName.equals("upload_session_id")) { - this.uploadSessionId = value.asString(); - } else if (memberName.equals("part_size")) { - this.partSize = Double.valueOf(value.toString()).longValue(); - } else if (memberName.equals("session_endpoints")) { - this.sessionEndpoints = new Endpoints(value.asObject()); - } else if (memberName.equals("total_parts")) { - this.totalParts = value.asInt(); - } if (memberName.equals("num_parts_processed")) { - this.partsProcessed = value.asInt(); - } - } - - public class Endpoints extends BoxJSONObject { - private URL listPartsEndpoint; - private URL commitEndpoint; - private URL uploadPartEndpoint; - private URL statusEndpoint; - private URL abortEndpoint; - - public Endpoints() { - super(); - } - - public Endpoints(String json) { - super(json); - } - - Endpoints(JsonObject jsonObject) { - super(jsonObject); - } - - public URL getListPartsEndpoint() { - return listPartsEndpoint; - } - - public URL getCommitEndpoint() { - return commitEndpoint; - } - - public URL getUploadPartEndpoint() { - return uploadPartEndpoint; - } - - public URL getStatusEndpoint() { - return statusEndpoint; - } - - public URL getAbortEndpoint() { - return abortEndpoint; - } - - @Override - protected void parseJSONMember(JsonObject.Member member) { - - String memberName = member.getName(); - JsonValue value = member.getValue(); - try { - if (memberName.equals("list_parts")) { - this.listPartsEndpoint = new URL(value.asString()); - } else if (memberName.equals("commit")) { - this.commitEndpoint = new URL(value.asString()); - } else if (memberName.equals("upload_part")) { - this.uploadPartEndpoint = new URL(value.asString()); - } else if (memberName.equals("status")) { - this.statusEndpoint = new URL(value.asString()); - } else if (memberName.equals("abort")) { - this.abortEndpoint = new URL(value.asString()); - } - } catch(MalformedURLException mue) { - assert false : "A ParseException indicates a bug in the SDK."; - } - } - } - - /** - * - */ - public void uploadPart(String xBoxPartId, byte[] bytes, long offset, Long partSize, long totalSizeOfFile, String contentRangeUnits) throws MalformedURLException, NoSuchAlgorithmException { - BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), this.getSessionEndpoints().getUploadPartEndpoint(), HttpMethod.POST); - request.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_OCTET_STREAM); - request.addHeader(HttpHeaders.X_BOX_PART_ID, xBoxPartId); - byte[] digestBytes = MessageDigest.getInstance("SHA1").digest(bytes); - String digest = Base64.encode(digestBytes); - request.addHeader("Digest", "sha=" + digest); - if(null == contentRangeUnits){ - contentRangeUnits = "bytes"; - } - request.addHeader(HttpHeaders.CONTENT_RANGE, contentRangeUnits+" "+offset+"-"+(offset+partSize-1)+"/"+totalSizeOfFile); - request.addHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(bytes.length)); - request.setBody(new ByteArrayInputStream(bytes)); - BoxJSONResponse response = (BoxJSONResponse) request.send(); - } - - public BoxFileUploadSession getUploadSessionStatus(String sessionId) { - BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), this.getSessionEndpoints().getStatusEndpoint(), "GET"); - BoxJSONResponse response = (BoxJSONResponse) request.send(); - JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); - System.out.println("Response: " + jsonObject); - - return new BoxFileUploadSession(this.getAPI(), jsonObject); - } - - - public void abortUploadSession(String sessionId) { - BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), this.getSessionEndpoints().getAbortEndpoint(), "DELETE"); - BoxAPIResponse response = (BoxAPIResponse) request.send(); - System.out.println("Abort session status: " + response.getResponseCode()); - } +public class BoxFileUploadSession extends BoxResource { + + private static final String DIGEST_HEADER_PREFIX_SHA = "sha="; + private static final String DIGEST_ALGORITHM_SHA1 = "SHA1"; + + private static final String MARKER_QUERY_STRING = "marker"; + private static final String LIMIT_QUERY_STRING = "limit"; + + private Info sessionInfo; + + BoxFileUploadSession(BoxAPIConnection api, String id) { + super(api, id); + } + + public class Info extends BoxResource.Info { + private BoxAPIConnection api; + private Date sessionExpiresAt; + private String uploadSessionId; + private Endpoints sessionEndpoints; + private long partSize; + private int totalParts; + private int partsProcessed; + + /** + * Constructs an empty Info object. + */ + public Info() { + super(); + BoxFileUploadSession.this.sessionInfo = this; + } + + /** + * Constructs an Info object by parsing information from a JSON string. + * @param json the JSON string to parse. + */ + public Info(String json) { + super(json); + BoxFileUploadSession.this.sessionInfo = this; + } + + /** + * Constructs an Info object using an already parsed JSON object. + * @param jsonObject the parsed JSON object. + */ + Info(JsonObject jsonObject) { + super(jsonObject); + BoxFileUploadSession.this.sessionInfo = this; + } + + private BoxAPIConnection getAPI() { + return this.api; + } + + private void setAPI(BoxAPIConnection api) { + this.api = api; + } + + public BoxFileUploadSession getResource() { + return BoxFileUploadSession.this; + } + + public int getTotalParts() { + return this.totalParts; + } + + public int getPartsProcessed() { + return this.partsProcessed; + } + + public Date getSessionExpiresAt() { + return this.sessionExpiresAt; + } + + public String getUploadSessionId() { + return this.uploadSessionId; + } + + public Endpoints getSessionEndpoints() { + return this.sessionEndpoints; + } + + public long getPartSize() { + return this.partSize; + } + + protected void parseJSONMember(JsonObject.Member member) { + + String memberName = member.getName(); + JsonValue value = member.getValue(); + if (memberName.equals("session_expires_at")) { + try { + String dateStr = value.asString(); + this.sessionExpiresAt = BoxDateFormat.parse(dateStr.substring(0, dateStr.length() - 1) + "-00:00"); + } catch (ParseException pe) { + assert false : "A ParseException indicates a bug in the SDK."; + } + } else if (memberName.equals("upload_session_id")) { + this.uploadSessionId = value.asString(); + } else if (memberName.equals("part_size")) { + this.partSize = Double.valueOf(value.toString()).longValue(); + } else if (memberName.equals("session_endpoints")) { + this.sessionEndpoints = new Endpoints(value.asObject()); + } else if (memberName.equals("total_parts")) { + this.totalParts = value.asInt(); + } else if (memberName.equals("num_parts_processed")) { + this.partsProcessed = value.asInt(); + } + } + } + + public class Endpoints extends BoxJSONObject { + private URL listPartsEndpoint; + private URL commitEndpoint; + private URL uploadPartEndpoint; + private URL statusEndpoint; + private URL abortEndpoint; + + public Endpoints() { + super(); + } + + public Endpoints(String json) { + super(json); + } + + Endpoints(JsonObject jsonObject) { + super(jsonObject); + } + + public URL getListPartsEndpoint() { + return this.listPartsEndpoint; + } + + public URL getCommitEndpoint() { + return this.commitEndpoint; + } + + public URL getUploadPartEndpoint() { + return this.uploadPartEndpoint; + } + + public URL getStatusEndpoint() { + return this.statusEndpoint; + } + + public URL getAbortEndpoint() { + return this.abortEndpoint; + } + + @Override + protected void parseJSONMember(JsonObject.Member member) { + + String memberName = member.getName(); + JsonValue value = member.getValue(); + try { + if (memberName.equals("list_parts")) { + this.listPartsEndpoint = new URL(value.asString()); + } else if (memberName.equals("commit")) { + this.commitEndpoint = new URL(value.asString()); + } else if (memberName.equals("upload_part")) { + this.uploadPartEndpoint = new URL(value.asString()); + } else if (memberName.equals("status")) { + this.statusEndpoint = new URL(value.asString()); + } else if (memberName.equals("abort")) { + this.abortEndpoint = new URL(value.asString()); + } + } catch (MalformedURLException mue) { + assert false : "A ParseException indicates a bug in the SDK."; + } + } + } + + public void uploadPart(String partId, byte[] bytes, long startRange, Long partSize, long totalSizeOfFile) + throws MalformedURLException, NoSuchAlgorithmException { + + URL uploadPartURL = this.sessionInfo.getSessionEndpoints().getUploadPartEndpoint(); + BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), uploadPartURL, HttpMethod.POST); + request.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_OCTET_STREAM); + request.addHeader(HttpHeaders.X_BOX_PART_ID, partId); + + byte[] digestBytes = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1).digest(bytes); + String digest = Base64.encode(digestBytes); + request.addHeader(HttpHeaders.DIGEST, DIGEST_HEADER_PREFIX_SHA + digest); + request.addHeader(HttpHeaders.CONTENT_RANGE, + "bytes " + startRange + "-" + (startRange + partSize - 1) + "/" + totalSizeOfFile); + request.addHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(bytes.length)); + + request.setBody(new ByteArrayInputStream(bytes)); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + } + + public int listParts(String sessionId, int marker, int limit) { + URL listPartsURL = this.sessionInfo.getSessionEndpoints().getListPartsEndpoint(); + URLTemplate template = new URLTemplate(listPartsURL.toString()); + + String queryString = new QueryStringBuilder() + .appendParam(MARKER_QUERY_STRING, marker) + .appendParam(LIMIT_QUERY_STRING, limit) + .toString(); + URL url = template.buildWithQuery("", queryString); + + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, HttpMethod.GET); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); +// System.out.println("Response: " + jsonObject); + + return response.getResponseCode(); + } + + public int commit(String sessionId, String digest, JsonObject jsonObject, String ifMatch, String ifNonMatch) { + URL commitURL = this.sessionInfo.getSessionEndpoints().getCommitEndpoint(); + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), commitURL, HttpMethod.POST); + request.addHeader(HttpHeaders.DIGEST, DIGEST_HEADER_PREFIX_SHA + digest); + request.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON); + + request.setBody(jsonObject.toString()); + + BoxAPIResponse response = request.send(); + //System.out.println("Response: " + response.getResponseCode()); + + try { + InputStream stream = response.getBody(); + int value = stream.read(); + StringBuffer buffer = new StringBuffer(); + while (value != -1) { + buffer.append(value); + value = stream.read(); + } + //System.out.println("Response: " + buffer.toString()); + } catch (Exception ex) { + ex.printStackTrace(); + } + + return response.getResponseCode(); + } + + public BoxFileUploadSession.Info getUploadSessionStatus() { + URL statusURL = this.sessionInfo.getSessionEndpoints().getStatusEndpoint(); + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), statusURL, HttpMethod.GET); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + + this.sessionInfo.update(jsonObject); + + return this.sessionInfo; + } + + public void abortUploadSession() { + URL abortURL = this.sessionInfo.getSessionEndpoints().getAbortEndpoint(); + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), abortURL, "DELETE"); + BoxAPIResponse response = request.send(); + //System.out.println("Abort session status: " + response.getResponseCode()); + } } diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index c6df62074..c2d933c78 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -729,7 +729,7 @@ public void deleteMetadata(String templateName, String scope) { response.disconnect(); } - public BoxFileUploadSession createUploadSession(String folderId, long fileSize, String fileName) { + public BoxFileUploadSession.Info createUploadSession(String folderId, long fileSize, String fileName) { String queryString = new QueryStringBuilder() .appendParam("folder_id", folderId) @@ -744,7 +744,10 @@ public BoxFileUploadSession createUploadSession(String folderId, long fileSize, JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); System.out.println("Response: " + jsonObject); - return new BoxFileUploadSession(this.getAPI(), jsonObject); + String sessionId = jsonObject.get("upload_session_id").asString(); + BoxFileUploadSession session = new BoxFileUploadSession(this.getAPI(), sessionId); + + return session.new Info(jsonObject); } /** diff --git a/src/main/java/com/box/sdk/BoxJSONRequest.java b/src/main/java/com/box/sdk/BoxJSONRequest.java index c67e51a40..1c86b1241 100644 --- a/src/main/java/com/box/sdk/BoxJSONRequest.java +++ b/src/main/java/com/box/sdk/BoxJSONRequest.java @@ -2,6 +2,8 @@ import java.net.URL; +import com.box.sdk.http.HttpMethod; + /** * Used to make HTTP requests containing JSON to the Box API. * @@ -23,6 +25,18 @@ public BoxJSONRequest(BoxAPIConnection api, URL url, String method) { this.addHeader("Content-Type", "application/json"); } + /** + * Constructs an authenticated BoxJSONRequest using a provided BoxAPIConnection. + * @param api an API connection for authenticating the request. + * @param url the URL of the request. + * @param method the HTTP method of the request. + */ + public BoxJSONRequest(BoxAPIConnection api, URL url, HttpMethod method) { + super(api, url, method); + + this.addHeader("Content-Type", "application/json"); + } + /** * Sets the body of this request to a given JSON string. * @param body the JSON string to use as the body. diff --git a/src/main/java/com/box/sdk/BoxResource.java b/src/main/java/com/box/sdk/BoxResource.java index 1dc4299bb..4deb7343e 100644 --- a/src/main/java/com/box/sdk/BoxResource.java +++ b/src/main/java/com/box/sdk/BoxResource.java @@ -60,6 +60,8 @@ private static Map> initResourceClassByType result.put(getResourceType(BoxLegalHoldPolicy.class), BoxLegalHoldPolicy.class); result.put(getResourceType(BoxLegalHoldAssignment.class), BoxLegalHoldAssignment.class); result.put(getResourceType(BoxFileVersionLegalHold.class), BoxFileVersionLegalHold.class); + result.put(getResourceType(BoxFileUploadSession.class), BoxFileUploadSession.class); + return Collections.unmodifiableMap(result); } diff --git a/src/main/java/com/box/sdk/http/ContentType.java b/src/main/java/com/box/sdk/http/ContentType.java index 4f6b4ebb4..c16312e1d 100644 --- a/src/main/java/com/box/sdk/http/ContentType.java +++ b/src/main/java/com/box/sdk/http/ContentType.java @@ -3,6 +3,12 @@ /** * */ -public class ContentType { - public static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; +public final class ContentType { + + public static final String APPLICATION_JSON = "application/json"; + public static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; + + //Prevents instantiation + private ContentType() { + } } diff --git a/src/main/java/com/box/sdk/http/HttpHeaders.java b/src/main/java/com/box/sdk/http/HttpHeaders.java index fa9b12404..ecb942aa1 100644 --- a/src/main/java/com/box/sdk/http/HttpHeaders.java +++ b/src/main/java/com/box/sdk/http/HttpHeaders.java @@ -3,10 +3,15 @@ /** * */ -public class HttpHeaders { - public static final String CONTENT_TYPE = "Content-Type"; - public static final String CONTENT_LENGTH = "Content-Length"; - public static final String CONTENT_RANGE = "Content-Range"; - public static final String DIGEST = "Digest"; - public static final String X_BOX_PART_ID = "X-Box-Part-Id"; +public final class HttpHeaders { + + public static final String CONTENT_LENGTH = "Content-Length"; + public static final String CONTENT_RANGE = "Content-Range"; + public static final String CONTENT_TYPE = "Content-Type"; + public static final String DIGEST = "Digest"; + public static final String X_BOX_PART_ID = "X-Box-Part-Id"; + + //Prevents instantiation + private HttpHeaders() { + } } diff --git a/src/main/java/com/box/sdk/http/HttpMethod.java b/src/main/java/com/box/sdk/http/HttpMethod.java index 4121191b5..062b94f54 100644 --- a/src/main/java/com/box/sdk/http/HttpMethod.java +++ b/src/main/java/com/box/sdk/http/HttpMethod.java @@ -4,5 +4,6 @@ * */ public enum HttpMethod { - GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; + + GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; } From d0e0b1a91e09acce97d3359b022c10831504775a Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Sun, 26 Feb 2017 23:05:02 -0800 Subject: [PATCH 057/119] Upload session list parts and commit are added --- .../com/box/sdk/BoxFileUploadSessionPart.java | 73 +++++++++++++++++++ .../box/sdk/BoxFileUploadSessionPartList.java | 57 +++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 src/main/java/com/box/sdk/BoxFileUploadSessionPart.java create mode 100644 src/main/java/com/box/sdk/BoxFileUploadSessionPartList.java diff --git a/src/main/java/com/box/sdk/BoxFileUploadSessionPart.java b/src/main/java/com/box/sdk/BoxFileUploadSessionPart.java new file mode 100644 index 000000000..dad0d21be --- /dev/null +++ b/src/main/java/com/box/sdk/BoxFileUploadSessionPart.java @@ -0,0 +1,73 @@ +package com.box.sdk; + +import com.eclipsesource.json.JsonObject; +import com.eclipsesource.json.JsonValue; + +/** + * + */ +public class BoxFileUploadSessionPart extends BoxJSONObject { + + private String partId; + private long offset; + private long size; + + /** + * Constructs an empty BoxFileUploadSessionPart object. + */ + public BoxFileUploadSessionPart() { + super(); + } + + /** + * Constructs an BoxFileUploadSessionPart object by parsing information from a JSON string. + * @param json the JSON string to parse. + */ + public BoxFileUploadSessionPart(String json) { + super(json); + } + + /** + * Constructs an BoxFileUploadSessionPart object using an already parsed JSON object. + * @param jsonObject the parsed JSON object. + */ + BoxFileUploadSessionPart(JsonObject jsonObject) { + super(jsonObject); + } + + public String getPartId() { + return this.partId; + } + + public long getOffset() { + return this.offset; + } + + public long getSize() { + return this.size; + } + + public void setPartId(String partId) { + this.partId = partId; + } + + public void setOffset(long offset) { + this.offset = offset; + } + + public void setSize(long size) { + this.size = size; + } + + protected void parseJSONMember(JsonObject.Member member) { + String memberName = member.getName(); + JsonValue value = member.getValue(); + if (memberName.equals("part_id")) { + this.partId = value.asString(); + } else if (memberName.equals("offset")) { + this.offset = Double.valueOf(value.toString()).longValue(); + } else if (memberName.equals("size")) { + this.size = Double.valueOf(value.toString()).longValue(); + } + } +} diff --git a/src/main/java/com/box/sdk/BoxFileUploadSessionPartList.java b/src/main/java/com/box/sdk/BoxFileUploadSessionPartList.java new file mode 100644 index 000000000..76ca989a1 --- /dev/null +++ b/src/main/java/com/box/sdk/BoxFileUploadSessionPartList.java @@ -0,0 +1,57 @@ +package com.box.sdk; + +import java.util.ArrayList; +import java.util.List; + +import com.eclipsesource.json.JsonArray; +import com.eclipsesource.json.JsonObject; +import com.eclipsesource.json.JsonValue; + +/** + * + */ +public class BoxFileUploadSessionPartList extends BoxJSONObject { + + private List parts; + private int marker; + + /** + * Constructs an BoxFileUploadSessionPart object using an already parsed JSON object. + * @param jsonObject the parsed JSON object. + */ + BoxFileUploadSessionPartList(JsonObject jsonObject) { + super(jsonObject); + } + + public List getParts() { + return this.parts; + } + + public int getMarker() { + return this.marker; + } + + protected void parseJSONMember(JsonObject.Member member) { + String memberName = member.getName(); + JsonValue value = member.getValue(); + if (memberName.equals("parts")) { + JsonArray array = (JsonArray) value; + + if (array.size() > 0) { + this.parts = this.getParts(array); + } + } else if (memberName.equals("marker")) { + this.marker = Double.valueOf(value.toString()).intValue(); + } + } + + private List getParts(JsonArray partsArray) { + List parts = new ArrayList(); + for (JsonValue value: partsArray) { + BoxFileUploadSessionPart part = new BoxFileUploadSessionPart((JsonObject) value); + parts.add(part); + } + + return parts; + } +} From 0f374d0d6d0f54b1327cd2ce793fff8889e17ea5 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Sun, 26 Feb 2017 23:05:25 -0800 Subject: [PATCH 058/119] Upload session list parts and commit are added --- .../com/box/sdk/BoxFileUploadSession.java | 62 +++++++++++++------ 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index 7cae4c53f..396f35042 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -8,16 +8,18 @@ import java.security.NoSuchAlgorithmException; import java.text.ParseException; import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Random; import com.box.sdk.http.ContentType; 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; -/** - * - */ +@BoxResourceType("upload-session") public class BoxFileUploadSession extends BoxResource { private static final String DIGEST_HEADER_PREFIX_SHA = "sha="; @@ -135,14 +137,6 @@ public class Endpoints extends BoxJSONObject { private URL statusEndpoint; private URL abortEndpoint; - public Endpoints() { - super(); - } - - public Endpoints(String json) { - super(json); - } - Endpoints(JsonObject jsonObject) { super(jsonObject); } @@ -194,7 +188,8 @@ public void uploadPart(String partId, byte[] bytes, long startRange, Long partSi throws MalformedURLException, NoSuchAlgorithmException { URL uploadPartURL = this.sessionInfo.getSessionEndpoints().getUploadPartEndpoint(); - BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), uploadPartURL, HttpMethod.POST); + + BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), uploadPartURL, HttpMethod.PUT); request.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_OCTET_STREAM); request.addHeader(HttpHeaders.X_BOX_PART_ID, partId); @@ -203,13 +198,12 @@ public void uploadPart(String partId, byte[] bytes, long startRange, Long partSi request.addHeader(HttpHeaders.DIGEST, DIGEST_HEADER_PREFIX_SHA + digest); request.addHeader(HttpHeaders.CONTENT_RANGE, "bytes " + startRange + "-" + (startRange + partSize - 1) + "/" + totalSizeOfFile); - request.addHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(bytes.length)); request.setBody(new ByteArrayInputStream(bytes)); - BoxJSONResponse response = (BoxJSONResponse) request.send(); + BoxAPIResponse response = (BoxAPIResponse) request.send(); } - public int listParts(String sessionId, int marker, int limit) { + public BoxFileUploadSessionPartList listParts(int marker, int limit) { URL listPartsURL = this.sessionInfo.getSessionEndpoints().getListPartsEndpoint(); URLTemplate template = new URLTemplate(listPartsURL.toString()); @@ -222,18 +216,21 @@ public int listParts(String sessionId, int marker, int limit) { BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, HttpMethod.GET); BoxJSONResponse response = (BoxJSONResponse) request.send(); JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); -// System.out.println("Response: " + jsonObject); + //System.out.println("List parts: " + jsonObject); - return response.getResponseCode(); + return new BoxFileUploadSessionPartList(jsonObject); } - public int commit(String sessionId, String digest, JsonObject jsonObject, String ifMatch, String ifNonMatch) { + public int commit(String digest, List parts, + Map attributes, String ifMatch, String ifNonMatch) { + URL commitURL = this.sessionInfo.getSessionEndpoints().getCommitEndpoint(); BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), commitURL, HttpMethod.POST); request.addHeader(HttpHeaders.DIGEST, DIGEST_HEADER_PREFIX_SHA + digest); request.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON); - request.setBody(jsonObject.toString()); + String body = this.getCommitBody(parts, attributes); + request.setBody(body); BoxAPIResponse response = request.send(); //System.out.println("Response: " + response.getResponseCode()); @@ -254,6 +251,31 @@ public int commit(String sessionId, String digest, JsonObject jsonObject, String return response.getResponseCode(); } + private String getCommitBody(List parts, Map attributes) { + JsonObject jsonObject = new JsonObject(); + + JsonArray array = new JsonArray(); + for (BoxFileUploadSessionPart part: parts) { + JsonObject partObj = new JsonObject(); + partObj.add("part_id", part.getPartId()); + partObj.add("offset", part.getOffset()); + partObj.add("size", part.getSize()); + + array.add(partObj); + } + jsonObject.add("parts", array); + + if (attributes != null) { + JsonObject attrObj = new JsonObject(); + for (String key: attributes.keySet()) { + attrObj.add(key, attributes.get(key)); + } + jsonObject.add("attributes", attrObj); + } + + return jsonObject.toString(); + } + public BoxFileUploadSession.Info getUploadSessionStatus() { URL statusURL = this.sessionInfo.getSessionEndpoints().getStatusEndpoint(); BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), statusURL, HttpMethod.GET); @@ -267,7 +289,7 @@ public BoxFileUploadSession.Info getUploadSessionStatus() { public void abortUploadSession() { URL abortURL = this.sessionInfo.getSessionEndpoints().getAbortEndpoint(); - BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), abortURL, "DELETE"); + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), abortURL, HttpMethod.DELETE); BoxAPIResponse response = request.send(); //System.out.println("Abort session status: " + response.getResponseCode()); } From 088ff528d1e0b3bfc54d6268b49706d381db6254 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Mon, 27 Feb 2017 11:20:31 -0800 Subject: [PATCH 059/119] Commit operation returning BoxFile --- .../com/box/sdk/BoxFileUploadSession.java | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index 396f35042..c057840aa 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -221,7 +221,7 @@ public BoxFileUploadSessionPartList listParts(int marker, int limit) { return new BoxFileUploadSessionPartList(jsonObject); } - public int commit(String digest, List parts, + public BoxFile.Info commit(String digest, List parts, Map attributes, String ifMatch, String ifNonMatch) { URL commitURL = this.sessionInfo.getSessionEndpoints().getCommitEndpoint(); @@ -232,23 +232,13 @@ public int commit(String digest, List parts, String body = this.getCommitBody(parts, attributes); request.setBody(body); - BoxAPIResponse response = request.send(); + BoxJSONResponse response = (BoxJSONResponse) request.send(); //System.out.println("Response: " + response.getResponseCode()); - try { - InputStream stream = response.getBody(); - int value = stream.read(); - StringBuffer buffer = new StringBuffer(); - while (value != -1) { - buffer.append(value); - value = stream.read(); - } - //System.out.println("Response: " + buffer.toString()); - } catch (Exception ex) { - ex.printStackTrace(); - } + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + BoxFile file = new BoxFile(this.getAPI(), jsonObject.get("id").toString()); - return response.getResponseCode(); + return file.new Info(jsonObject); } private String getCommitBody(List parts, Map attributes) { From 94e3bac30d4991c816c7969dbe96f71f15fcb9e4 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Mon, 27 Feb 2017 12:00:58 -0800 Subject: [PATCH 060/119] If-Match and If-None-Match headers implementation --- src/main/java/com/box/sdk/BoxFile.java | 1 - .../com/box/sdk/BoxFileUploadSession.java | 19 +++++++++++-------- src/main/java/com/box/sdk/BoxFolder.java | 1 - 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index bda7859e5..b9e31b834 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -871,7 +871,6 @@ public BoxFileUploadSession.Info createUploadSession(long fileSize) { BoxJSONResponse response = (BoxJSONResponse) request.send(); JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); - System.out.println("Response: " + jsonObject); String sessionId = jsonObject.get("upload_session_id").asString(); BoxFileUploadSession session = new BoxFileUploadSession(this.getAPI(), sessionId); diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index c057840aa..41d8a8bc3 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -1,7 +1,6 @@ package com.box.sdk; import java.io.ByteArrayInputStream; -import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.security.MessageDigest; @@ -10,7 +9,6 @@ import java.util.Date; import java.util.List; import java.util.Map; -import java.util.Random; import com.box.sdk.http.ContentType; import com.box.sdk.http.HttpHeaders; @@ -200,7 +198,7 @@ public void uploadPart(String partId, byte[] bytes, long startRange, Long partSi "bytes " + startRange + "-" + (startRange + partSize - 1) + "/" + totalSizeOfFile); request.setBody(new ByteArrayInputStream(bytes)); - BoxAPIResponse response = (BoxAPIResponse) request.send(); + request.send(); } public BoxFileUploadSessionPartList listParts(int marker, int limit) { @@ -216,24 +214,30 @@ public BoxFileUploadSessionPartList listParts(int marker, int limit) { BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, HttpMethod.GET); BoxJSONResponse response = (BoxJSONResponse) request.send(); JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); - //System.out.println("List parts: " + jsonObject); return new BoxFileUploadSessionPartList(jsonObject); } public BoxFile.Info commit(String digest, List parts, - Map attributes, String ifMatch, String ifNonMatch) { + Map attributes, String ifMatch, String ifNoneMatch) { URL commitURL = this.sessionInfo.getSessionEndpoints().getCommitEndpoint(); BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), commitURL, HttpMethod.POST); request.addHeader(HttpHeaders.DIGEST, DIGEST_HEADER_PREFIX_SHA + digest); request.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON); + if (ifMatch != null) { + request.addHeader(HttpHeaders.IF_MATCH, ifMatch); + } + + if (ifNoneMatch != null) { + request.addHeader(HttpHeaders.IF_NONE_MATCH, ifNoneMatch); + } + String body = this.getCommitBody(parts, attributes); request.setBody(body); BoxJSONResponse response = (BoxJSONResponse) request.send(); - //System.out.println("Response: " + response.getResponseCode()); JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); BoxFile file = new BoxFile(this.getAPI(), jsonObject.get("id").toString()); @@ -280,7 +284,6 @@ public BoxFileUploadSession.Info getUploadSessionStatus() { public void abortUploadSession() { URL abortURL = this.sessionInfo.getSessionEndpoints().getAbortEndpoint(); BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), abortURL, HttpMethod.DELETE); - BoxAPIResponse response = request.send(); - //System.out.println("Abort session status: " + response.getResponseCode()); + request.send(); } } diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index c2d933c78..dd10f4e4b 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -742,7 +742,6 @@ public BoxFileUploadSession.Info createUploadSession(String folderId, long fileS BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); BoxJSONResponse response = (BoxJSONResponse) request.send(); JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); - System.out.println("Response: " + jsonObject); String sessionId = jsonObject.get("upload_session_id").asString(); BoxFileUploadSession session = new BoxFileUploadSession(this.getAPI(), sessionId); From ed0ecab538268d55de308ff674fbeaaa7ae4d6f2 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Mon, 27 Feb 2017 12:01:45 -0800 Subject: [PATCH 061/119] If-Match and If-None-Match headers implementation --- src/main/java/com/box/sdk/http/HttpHeaders.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/box/sdk/http/HttpHeaders.java b/src/main/java/com/box/sdk/http/HttpHeaders.java index ecb942aa1..f9b151afa 100644 --- a/src/main/java/com/box/sdk/http/HttpHeaders.java +++ b/src/main/java/com/box/sdk/http/HttpHeaders.java @@ -9,6 +9,8 @@ public final class HttpHeaders { public static final String CONTENT_RANGE = "Content-Range"; public static final String CONTENT_TYPE = "Content-Type"; public static final String DIGEST = "Digest"; + public static final String IF_MATCH = "If-Match"; + public static final String IF_NONE_MATCH = "If-None-Match"; public static final String X_BOX_PART_ID = "X-Box-Part-Id"; //Prevents instantiation From 515d9fed2f85851035b4ea56ef0d9e30da303a3e Mon Sep 17 00:00:00 2001 From: David Maynard Date: Tue, 28 Feb 2017 13:31:58 -0800 Subject: [PATCH 062/119] Fix event stream to handle no events in the stream --- src/test/java/com/box/sdk/EventStreamTest.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/box/sdk/EventStreamTest.java b/src/test/java/com/box/sdk/EventStreamTest.java index 67bf16d2d..3dc86c72a 100644 --- a/src/test/java/com/box/sdk/EventStreamTest.java +++ b/src/test/java/com/box/sdk/EventStreamTest.java @@ -57,8 +57,14 @@ public boolean onException(Throwable e) { boolean createdEventFound = false; boolean deletedEventFound = false; - while (!createdEventFound || !deletedEventFound) { + int timeouts = 0; + while ( timeouts < 3 && (!createdEventFound || !deletedEventFound)) { BoxEvent event = observedEvents.poll(1, TimeUnit.MINUTES); + if( null == event) { + timeouts++; + System.out.println("Time outs: " + timeouts); + continue; + } BoxResource.Info sourceInfo = event.getSourceInfo(); // Some events may not have sourceInfo if (sourceInfo == null) { From 8272cb85391dffc960fb727d39f921401f7f6dd1 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Tue, 28 Feb 2017 13:43:10 -0800 Subject: [PATCH 063/119] Fix checkstyle whitespace issues --- src/test/java/com/box/sdk/EventStreamTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/box/sdk/EventStreamTest.java b/src/test/java/com/box/sdk/EventStreamTest.java index 3dc86c72a..eef8b677b 100644 --- a/src/test/java/com/box/sdk/EventStreamTest.java +++ b/src/test/java/com/box/sdk/EventStreamTest.java @@ -58,9 +58,9 @@ public boolean onException(Throwable e) { boolean createdEventFound = false; boolean deletedEventFound = false; int timeouts = 0; - while ( timeouts < 3 && (!createdEventFound || !deletedEventFound)) { + while (timeouts < 3 && (!createdEventFound || !deletedEventFound)) { BoxEvent event = observedEvents.poll(1, TimeUnit.MINUTES); - if( null == event) { + if (null == event) { timeouts++; System.out.println("Time outs: " + timeouts); continue; From 21d860b85568693dfe5a25708a79c70f4f8d7e7a Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Tue, 28 Feb 2017 16:26:59 -0800 Subject: [PATCH 064/119] Integration tests for the commit --- src/main/java/com/box/sdk/BoxAPIRequest.java | 2 +- .../com/box/sdk/BoxFileUploadSession.java | 7 +- src/main/java/com/box/sdk/BoxFolder.java | 4 +- src/test/java/com/box/sdk/BoxFileTest.java | 213 ++++++++++++++++-- src/test/java/com/box/sdk/BoxFolderTest.java | 64 ++++-- 5 files changed, 256 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxAPIRequest.java b/src/main/java/com/box/sdk/BoxAPIRequest.java index de7b3585a..62eb7cc6f 100644 --- a/src/main/java/com/box/sdk/BoxAPIRequest.java +++ b/src/main/java/com/box/sdk/BoxAPIRequest.java @@ -425,7 +425,7 @@ private BoxAPIResponse trySend(ProgressListener listener) { BoxAPIResponse response; if (contentType == null) { response = new BoxAPIResponse(connection); - } else if (contentType.contains("application/json")) { + } else if (contentType.contains("application/json") || contentType.contains("text/plain")) { response = new BoxJSONResponse(connection); } else { response = new BoxAPIResponse(connection); diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index 41d8a8bc3..21f512e20 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -182,7 +182,7 @@ protected void parseJSONMember(JsonObject.Member member) { } } - public void uploadPart(String partId, byte[] bytes, long startRange, Long partSize, long totalSizeOfFile) + public void uploadPart(String partId, byte[] bytes, long offset, Long partSize, long totalSizeOfFile) throws MalformedURLException, NoSuchAlgorithmException { URL uploadPartURL = this.sessionInfo.getSessionEndpoints().getUploadPartEndpoint(); @@ -195,7 +195,7 @@ public void uploadPart(String partId, byte[] bytes, long startRange, Long partSi String digest = Base64.encode(digestBytes); request.addHeader(HttpHeaders.DIGEST, DIGEST_HEADER_PREFIX_SHA + digest); request.addHeader(HttpHeaders.CONTENT_RANGE, - "bytes " + startRange + "-" + (startRange + partSize - 1) + "/" + totalSizeOfFile); + "bytes " + offset + "-" + (offset + partSize - 1) + "/" + totalSizeOfFile); request.setBody(new ByteArrayInputStream(bytes)); request.send(); @@ -238,9 +238,8 @@ public BoxFile.Info commit(String digest, List parts, request.setBody(body); BoxJSONResponse response = (BoxJSONResponse) request.send(); - JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); - BoxFile file = new BoxFile(this.getAPI(), jsonObject.get("id").toString()); + BoxFile file = new BoxFile(this.getAPI(), jsonObject.get("id").asString()); return file.new Info(jsonObject); } diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index dd10f4e4b..45d9c5d20 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -729,10 +729,10 @@ public void deleteMetadata(String templateName, String scope) { response.disconnect(); } - public BoxFileUploadSession.Info createUploadSession(String folderId, long fileSize, String fileName) { + public BoxFileUploadSession.Info createUploadSession(String fileName, long fileSize) { String queryString = new QueryStringBuilder() - .appendParam("folder_id", folderId) + .appendParam("folder_id", this.getID()) .appendParam("file_size", fileSize) .appendParam("file_name", fileName) .toString(); diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index 19ad7a168..d073c6e37 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -10,6 +10,8 @@ import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; +import java.security.DigestInputStream; +import java.security.MessageDigest; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -17,6 +19,8 @@ import java.util.Collection; import java.util.Date; import java.util.Iterator; +import java.util.List; +import java.util.Random; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; @@ -968,17 +972,182 @@ public void setCollectionsWithInfoSucceeds() { @Test @Category(IntegrationTest.class) - public void uploadSessionSucceeds() { + public void uploadSessionCommitFlowSuccess() throws Exception { BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); BoxFolder rootFolder = BoxFolder.getRootFolder(api); - String fileName = "[createUploadSessionSucceeds] Test File.txt"; + + BoxFile uploadedFile = null; + try { + String fileName = "Tamme-Lauri_tamm_suvepäeval.jpg"; + URL fileURL = this.getClass().getResource("/sample-files/" + fileName); + String filePath = URLDecoder.decode(fileURL.getFile(), "utf-8"); + File file = new File(filePath); + long fileSize = file.length(); + + //Create the session + BoxFileUploadSession.Info session = createFileUploadSession(rootFolder, fileName, fileSize); + + //Create the parts + MessageDigest fileDigest = uploadParts(uploadedFile, session, fileSize); + + //List the session parts + List parts = listUploadSessionParts(session.getResource()); + + byte[] digestBytes = fileDigest.digest(); + String digest = Base64.encode(digestBytes); + + //Verify the delete session + uploadedFile = commitSession(session.getResource(), digest, parts); + } finally { + if (uploadedFile != null) { + uploadedFile.delete(); + } + } + } + + private BoxFileUploadSession.Info createFileUploadSession(BoxFolder folder, String fileName, long fileSize) { + BoxFileUploadSession.Info session = folder.createUploadSession(fileName, fileSize); + Assert.assertNotNull(session.getUploadSessionId()); + Assert.assertNotNull(session.getSessionExpiresAt()); + Assert.assertNotNull(session.getPartSize()); + + BoxFileUploadSession.Endpoints endpoints = session.getSessionEndpoints(); + Assert.assertNotNull(endpoints); + Assert.assertNotNull(endpoints.getUploadPartEndpoint()); + Assert.assertNotNull(endpoints.getStatusEndpoint()); + Assert.assertNotNull(endpoints.getListPartsEndpoint()); + Assert.assertNotNull(endpoints.getCommitEndpoint()); + Assert.assertNotNull(endpoints.getAbortEndpoint()); + + return session; + } + + + @Test + @Category(IntegrationTest.class) + public void uploadSessionVersionCommitFlowSuccess() throws Exception { + BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); + BoxFolder rootFolder = BoxFolder.getRootFolder(api); + + BoxFile.Info imageFileInfo = createImageFile(rootFolder); + + BoxFile uploadedFile = imageFileInfo.getResource(); + try { + //Create the session + BoxFileUploadSession.Info session = createFileUploadSession(uploadedFile, imageFileInfo.getSize()); + + //Create the parts + MessageDigest fileDigest = uploadParts(uploadedFile, session, imageFileInfo.getSize()); + + //List the session parts + List parts = listUploadSessionParts(session.getResource()); + + byte[] digestBytes = fileDigest.digest(); + String digest = Base64.encode(digestBytes); + + //Verify the delete session + uploadedFile = commitSession(session.getResource(), digest, parts); + } finally { + uploadedFile.delete(); + } + } + + private BoxFileUploadSession.Info createFileUploadSession(BoxFile uploadedFile, long fileSize) { + BoxFileUploadSession.Info session = uploadedFile.createUploadSession(fileSize); + Assert.assertNotNull(session.getUploadSessionId()); + Assert.assertNotNull(session.getSessionExpiresAt()); + Assert.assertNotNull(session.getPartSize()); + + BoxFileUploadSession.Endpoints endpoints = session.getSessionEndpoints(); + Assert.assertNotNull(endpoints); + Assert.assertNotNull(endpoints.getUploadPartEndpoint()); + Assert.assertNotNull(endpoints.getStatusEndpoint()); + Assert.assertNotNull(endpoints.getListPartsEndpoint()); + Assert.assertNotNull(endpoints.getCommitEndpoint()); + Assert.assertNotNull(endpoints.getAbortEndpoint()); + + return session; + } + + private MessageDigest uploadParts(BoxFile uploadedFile, BoxFileUploadSession.Info session, + long fileSize) throws Exception { + + String fileName = "Tamme-Lauri_tamm_suvepäeval.jpg"; + URL fileURL = this.getClass().getResource("/sample-files/" + fileName); + String filePath = URLDecoder.decode(fileURL.getFile(), "utf-8"); + File file = new File(filePath); + + FileInputStream stream = new FileInputStream(file); + MessageDigest fileDigest = MessageDigest.getInstance("SHA1"); + DigestInputStream dis = new DigestInputStream(stream, fileDigest); + + long offset = 0; + byte[] bytes = null; + long processed = 0; + boolean canBreak = false; + while(true) { + long min = session.getPartSize(); + long diff = fileSize - processed; + if (diff < min) { + min = diff; + canBreak = true; + } + + bytes = new byte[(int) min]; + dis.read(bytes); + + session.getResource().uploadPart(generateHex(), bytes, offset, min, fileSize); + + offset = offset + session.getPartSize(); + processed += min; + if (canBreak) { + break; + } + } + + return fileDigest; + } + + private String generateHex() { + String hex = ""; + while (hex.length() != 8) { + Random random = new Random(); + int val = random.nextInt(); + hex = Integer.toHexString(val); + } + + return hex; + } + + private BoxFile.Info createImageFile(BoxFolder folder) throws IOException { + String fileName = "Tamme-Lauri_tamm_suvepäeval.jpg"; + URL fileURL = this.getClass().getResource("/sample-files/" + fileName); + String filePath = URLDecoder.decode(fileURL.getFile(), "utf-8"); + File file = new File(filePath); + long fileSize = file.length(); + + FileInputStream stream = new FileInputStream(file); + + byte[] fileBytes = new byte[(int) fileSize]; + + InputStream uploadStream = new ByteArrayInputStream(fileBytes); + return folder.uploadFile(uploadStream, fileName); + } + + @Test + @Category(IntegrationTest.class) + public void uploadSessionAbortFlowSuccess() throws Exception { + BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); + BoxFolder rootFolder = BoxFolder.getRootFolder(api); + + String fileName = "[setCollectionsWithInfoSucceeds] Test File.txt"; String fileContent = "Test file"; byte[] fileBytes = fileContent.getBytes(StandardCharsets.UTF_8); InputStream uploadStream = new ByteArrayInputStream(fileBytes); BoxFile uploadedFile = rootFolder.uploadFile(uploadStream, fileName).getResource(); try { - BoxFileUploadSession session = uploadedFile.createUploadSession(10000000); + BoxFileUploadSession.Info session = uploadedFile.createUploadSession(fileBytes.length); Assert.assertNotNull(session.getUploadSessionId()); Assert.assertNotNull(session.getSessionExpiresAt()); Assert.assertNotNull(session.getPartSize()); @@ -992,28 +1161,44 @@ public void uploadSessionSucceeds() { Assert.assertNotNull(endpoints.getAbortEndpoint()); //Verify the status of the session - getUploadSessionStatusSucceeds(uploadedFile, session.getUploadSessionId()); + getUploadSessionStatus(session.getResource()); //Verify the delete session - abortUploadSessionStatusSucceeds(uploadedFile, session.getUploadSessionId()); + abortUploadSession(session.getResource()); } finally { uploadedFile.delete(); } } - private void getUploadSessionStatusSucceeds(BoxFile file, String sessionId) { - BoxFileUploadSession session = file.getUploadSessionStatus(sessionId); - Assert.assertNotNull(session.getSessionExpiresAt()); - Assert.assertNotNull(session.getPartSize()); - Assert.assertNotNull(session.getTotalParts()); - Assert.assertNotNull(session.getPartsProcessed()); + private List listUploadSessionParts(BoxFileUploadSession session) { + BoxFileUploadSessionPartList list = session.listParts(0, 10); + + List parts = list.getParts(); + Assert.assertEquals(parts.size(), 3); + + return parts; + } + + private BoxFile commitSession(BoxFileUploadSession session, String digest, List parts) { + BoxFile.Info file = session.commit(digest, parts, null, null, null); + Assert.assertNotNull(file); + + return file.getResource(); + } + + private void getUploadSessionStatus(BoxFileUploadSession session) { + BoxFileUploadSession.Info sessionInfo = session.getUploadSessionStatus(); + Assert.assertNotNull(sessionInfo.getSessionExpiresAt()); + Assert.assertNotNull(sessionInfo.getPartSize()); + Assert.assertNotNull(sessionInfo.getTotalParts()); + Assert.assertNotNull(sessionInfo.getPartsProcessed()); } - private void abortUploadSessionStatusSucceeds(BoxFile file, String sessionId) { - file.abortUploadSession(sessionId); + private void abortUploadSession(BoxFileUploadSession session) { + session.abortUploadSession(); try { - BoxFileUploadSession session = file.getUploadSessionStatus(sessionId); + BoxFileUploadSession.Info sessionInfo = session.getUploadSessionStatus(); //If the session is aborted, this line should not be executed. Assert.assertFalse("Upload session is not deleted", true); diff --git a/src/test/java/com/box/sdk/BoxFolderTest.java b/src/test/java/com/box/sdk/BoxFolderTest.java index c24f22642..5da3f0e12 100644 --- a/src/test/java/com/box/sdk/BoxFolderTest.java +++ b/src/test/java/com/box/sdk/BoxFolderTest.java @@ -1068,20 +1068,58 @@ public void sharedLinkInfoHasEffectiveAccess() { @Test @Category(IntegrationTest.class) - public void createUploadSessionSucceeds() { + public void uploadSessionAbortFlowSuccess() throws Exception { BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); BoxFolder rootFolder = BoxFolder.getRootFolder(api); - BoxFileUploadSession session = rootFolder.createUploadSession("0", 1000000, "Test_File.txt"); - Assert.assertNotNull(session.getUploadSessionId()); - Assert.assertNotNull(session.getSessionExpiresAt()); - Assert.assertNotNull(session.getPartSize()); - - BoxFileUploadSession.Endpoints endpoints = session.getSessionEndpoints(); - Assert.assertNotNull(endpoints); - Assert.assertNotNull(endpoints.getUploadPartEndpoint()); - Assert.assertNotNull(endpoints.getStatusEndpoint()); - Assert.assertNotNull(endpoints.getListPartsEndpoint()); - Assert.assertNotNull(endpoints.getCommitEndpoint()); - Assert.assertNotNull(endpoints.getAbortEndpoint()); + + String fileName = "[setCollectionsWithInfoSucceeds] Test File.txt"; + String fileContent = "Test file"; + byte[] fileBytes = fileContent.getBytes(StandardCharsets.UTF_8); + + InputStream uploadStream = new ByteArrayInputStream(fileBytes); + BoxFile uploadedFile = rootFolder.uploadFile(uploadStream, fileName).getResource(); + try { + BoxFileUploadSession.Info session = uploadedFile.createUploadSession(fileBytes.length); + Assert.assertNotNull(session.getUploadSessionId()); + Assert.assertNotNull(session.getSessionExpiresAt()); + Assert.assertNotNull(session.getPartSize()); + + BoxFileUploadSession.Endpoints endpoints = session.getSessionEndpoints(); + Assert.assertNotNull(endpoints); + Assert.assertNotNull(endpoints.getUploadPartEndpoint()); + Assert.assertNotNull(endpoints.getStatusEndpoint()); + Assert.assertNotNull(endpoints.getListPartsEndpoint()); + Assert.assertNotNull(endpoints.getCommitEndpoint()); + Assert.assertNotNull(endpoints.getAbortEndpoint()); + + //Verify the status of the session + getUploadSessionStatus(session.getResource()); + + //Verify the delete session + abortUploadSession(session.getResource()); + } finally { + uploadedFile.delete(); + } + } + + private void getUploadSessionStatus(BoxFileUploadSession session) { + BoxFileUploadSession.Info sessionInfo = session.getUploadSessionStatus(); + Assert.assertNotNull(sessionInfo.getSessionExpiresAt()); + Assert.assertNotNull(sessionInfo.getPartSize()); + Assert.assertNotNull(sessionInfo.getTotalParts()); + Assert.assertNotNull(sessionInfo.getPartsProcessed()); + } + + private void abortUploadSession(BoxFileUploadSession session) { + session.abortUploadSession(); + + try { + BoxFileUploadSession.Info sessionInfo = session.getUploadSessionStatus(); + + //If the session is aborted, this line should not be executed. + Assert.assertFalse("Upload session is not deleted", true); + } catch(BoxAPIException apiEx) { + Assert.assertEquals(apiEx.getResponseCode(), 404); + } } } From 06d545f193a01efeb27b67f120d38081feeb1e2c Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Tue, 28 Feb 2017 17:25:56 -0800 Subject: [PATCH 065/119] public api support for upload file is moved to 2.0 from 2.1 --- .../java/com/box/sdk/BoxAPIConnection.java | 2 +- src/main/java/com/box/sdk/BoxFile.java | 8 +++++--- .../com/box/sdk/BoxFileUploadSession.java | 19 ++++++++++++++++--- src/main/java/com/box/sdk/BoxFolder.java | 16 ++++++++-------- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxAPIConnection.java b/src/main/java/com/box/sdk/BoxAPIConnection.java index 0114ad76f..896e0d7c3 100644 --- a/src/main/java/com/box/sdk/BoxAPIConnection.java +++ b/src/main/java/com/box/sdk/BoxAPIConnection.java @@ -26,7 +26,7 @@ public class BoxAPIConnection { private static final String TOKEN_URL_STRING = "https://api.box.com/oauth2/token"; private static final String DEFAULT_BASE_URL = "https://api.box.com/2.0/"; private static final String DEFAULT_BASE_UPLOAD_URL = "https://upload.box.com/api/2.0/"; - private static final String DEFAULT_BASE_UPLOAD_SESSION_URL = "https://upload.app.box.com/api/2.1/"; + private static final String DEFAULT_BASE_UPLOAD_SESSION_URL = "https://upload.app.box.com/api/2.0/"; /** * The amount of buffer time, in milliseconds, to use when determining if an access token should be refreshed. For diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index b9e31b834..298a94d25 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -862,13 +862,15 @@ public BoxFile.Info setCollections(BoxCollection... collections) { } public BoxFileUploadSession.Info createUploadSession(long fileSize) { - String queryString = new QueryStringBuilder().appendParam("file_size", fileSize).toString(); - URL url = UPLOAD_SESSION_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseUploadSessionURL(), - queryString, this.getID()); + URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), this.getID()); BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); request.addHeader("Content-Type", "application/json"); + JsonObject body = new JsonObject(); + body.add("file_size", fileSize); + request.setBody(body.toString()); + BoxJSONResponse response = (BoxJSONResponse) request.send(); JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index 21f512e20..8ee6f7f9e 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -237,11 +237,24 @@ public BoxFile.Info commit(String digest, List parts, String body = this.getCommitBody(parts, attributes); request.setBody(body); - BoxJSONResponse response = (BoxJSONResponse) request.send(); + BoxAPIResponse response = (BoxAPIResponse) request.send(); + if (response instanceof BoxJSONResponse) { + return getFile((BoxJSONResponse) response); + } else { + throw new BoxAPIException("Commit response content type is not application/json. The response code : " + response.getResponseCode()); + } + } + + private BoxFile.Info getFile(BoxJSONResponse response) { JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); - BoxFile file = new BoxFile(this.getAPI(), jsonObject.get("id").asString()); + System.out.println("Response: " + response.getResponseCode() + " : " + response.getJSON()); + + JsonArray array = (JsonArray) jsonObject.get("entries"); + JsonObject fileObj = (JsonObject) array.get(0); + + BoxFile file = new BoxFile(this.getAPI(), fileObj.get("id").asString()); - return file.new Info(jsonObject); + return file.new Info(fileObj); } private String getCommitBody(List parts, Map attributes) { diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index 45d9c5d20..87867bb6e 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -731,15 +731,15 @@ public void deleteMetadata(String templateName, String scope) { public BoxFileUploadSession.Info createUploadSession(String fileName, long fileSize) { - String queryString = new QueryStringBuilder() - .appendParam("folder_id", this.getID()) - .appendParam("file_size", fileSize) - .appendParam("file_name", fileName) - .toString(); - - URL url = UPLOAD_SESSION_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseUploadSessionURL(), - queryString); + URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL()); BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); + + JsonObject body = new JsonObject(); + body.add("folder_id", this.getID()); + body.add("file_name", fileName); + body.add("file_size", fileSize); + request.setBody(body.toString()); + BoxJSONResponse response = (BoxJSONResponse) request.send(); JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); From 9196b0acd6eab70769f4e72ea7bb9115bd0e4788 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Wed, 1 Mar 2017 14:13:50 -0800 Subject: [PATCH 066/119] upload large file method is added --- src/main/java/com/box/sdk/BoxFile.java | 6 + src/main/java/com/box/sdk/BoxFolder.java | 6 + .../java/com/box/sdk/LargeFileUpload.java | 153 ++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 src/main/java/com/box/sdk/LargeFileUpload.java diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 298a94d25..b1bf9bf36 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -879,6 +879,12 @@ public BoxFileUploadSession.Info createUploadSession(long fileSize) { return session.new Info(jsonObject); } + public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize) throws Exception { + URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), this.getID()); + + return LargeFileUpload.upload(this.getAPI(), inputStream, url, fileSize); + } + /** * Contains information about a BoxFile. */ diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index 87867bb6e..cf49647d3 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -749,6 +749,12 @@ public BoxFileUploadSession.Info createUploadSession(String fileName, long fileS return session.new Info(jsonObject); } + public BoxFile.Info uploadLargeFile(InputStream inputStream, String fileName, long fileSize) throws Exception { + URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL()); + + return LargeFileUpload.upload(this.getAPI(), this.getID(), inputStream, url, fileName, fileSize); + } + /** * Contains information about a BoxFolder. */ diff --git a/src/main/java/com/box/sdk/LargeFileUpload.java b/src/main/java/com/box/sdk/LargeFileUpload.java new file mode 100644 index 000000000..159c52687 --- /dev/null +++ b/src/main/java/com/box/sdk/LargeFileUpload.java @@ -0,0 +1,153 @@ +package com.box.sdk; + +import com.box.sdk.*; +import com.box.sdk.http.ContentType; +import com.box.sdk.http.HttpHeaders; +import com.box.sdk.http.HttpMethod; +import com.eclipsesource.json.JsonObject; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Random; + +/** + * + */ +public final class LargeFileUpload { + + private static final String DIGEST_HEADER_PREFIX_SHA = "sha="; + private static final String DIGEST_ALGORITHM_SHA1 = "SHA1"; + + private static final String MARKER_QUERY_STRING = "marker"; + private static final String LIMIT_QUERY_STRING = "limit"; + + private LargeFileUpload() { + } + + static BoxFile.Info upload(BoxAPIConnection boxApi, String folderId, InputStream stream, URL url, + String fileName, long fileSize) throws Exception { + + BoxFileUploadSession.Info session = createUploadSession(boxApi, folderId, url, fileName, fileSize); + + MessageDigest digest = uploadParts(session, stream, fileSize); + byte[] digestBytes = digest.digest(); + String digestStr = Base64.encode(digestBytes); + + BoxFileUploadSessionPartList list = session.getResource().listParts(0, 1000); + try { + return session.getResource().commit(digestStr, list.getParts(), null, null, null); + } finally { + session.getResource().abortUploadSession(); + } + } + + private static BoxFileUploadSession.Info createUploadSession(BoxAPIConnection boxApi, String folderId, + URL url, String fileName, long fileSize) { + + BoxJSONRequest request = new BoxJSONRequest(boxApi, url, HttpMethod.POST); + + JsonObject body = new JsonObject(); + body.add("folder_id", folderId); + body.add("file_name", fileName); + body.add("file_size", fileSize); + request.setBody(body.toString()); + + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + + String sessionId = jsonObject.get("upload_session_id").asString(); + BoxFileUploadSession session = new BoxFileUploadSession(boxApi, sessionId); + + return session.new Info(jsonObject); + } + + static BoxFile.Info upload(BoxAPIConnection boxApi, InputStream stream, URL url, long fileSize) throws Exception { + BoxFileUploadSession.Info session = createUploadSession(boxApi, url, fileSize); + + MessageDigest digest = uploadParts(session, stream, fileSize); + byte[] digestBytes = digest.digest(); + String digestStr = Base64.encode(digestBytes); + + BoxFileUploadSessionPartList list = session.getResource().listParts(0, 1000); + try { + return session.getResource().commit(digestStr, list.getParts(), null, null, null); + } finally { + session.getResource().abortUploadSession(); + } + } + + private static BoxFileUploadSession.Info createUploadSession(BoxAPIConnection boxApi, URL url, long fileSize) { + BoxJSONRequest request = new BoxJSONRequest(boxApi, url, HttpMethod.POST); + JsonObject body = new JsonObject(); + body.add("file_size", fileSize); + request.setBody(body.toString()); + + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + + String sessionId = jsonObject.get("upload_session_id").asString(); + BoxFileUploadSession session = new BoxFileUploadSession(boxApi, sessionId); + + return session.new Info(jsonObject); + } + + private static MessageDigest uploadParts(BoxFileUploadSession.Info session, InputStream stream, long fileSize) + throws Exception { + + MessageDigest digest = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1); + DigestInputStream dis = new DigestInputStream(stream, digest); + + long partSize = session.getPartSize(); + long offset = 0; + long processed = 0; + while (processed < fileSize) { + long diff = fileSize - processed; + if (diff < partSize) { + partSize = diff; + } + + uploadPart(session.getResource(), dis, offset, partSize, fileSize); + + processed += partSize; + offset += partSize; + } + + return digest; + } + + private static void uploadPart(BoxFileUploadSession session, InputStream stream, long offset, + long partSize, long fileSize) throws Exception { + + String partId = generateHex(); + byte[] bytes = new byte[(int) partSize]; + stream.read(bytes); + + for(int i = 0; i < 3; i++) { + try { + session.uploadPart(partId, bytes, offset, partSize, fileSize); + break; + } catch (BoxAPIException ex) { + if (i == 2) { + throw ex; + } + } + } + } + + public static String generateHex() { + String hex = ""; + while (hex.length() != 8) { + Random random = new Random(); + int val = random.nextInt(); + hex = Integer.toHexString(val); + } + + return hex; + } + +} From b91fc4e279378134681d5bfe5cd626a9f526c413 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Wed, 1 Mar 2017 15:30:30 -0800 Subject: [PATCH 067/119] upload part takes stream as input --- .../com/box/sdk/BoxFileUploadSession.java | 9 ++++- .../java/com/box/sdk/LargeFileUpload.java | 4 +- src/test/java/com/box/sdk/BoxFileTest.java | 39 +++++++++++++++++-- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index 8ee6f7f9e..f04509f8d 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -1,6 +1,8 @@ package com.box.sdk; import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.security.MessageDigest; @@ -182,8 +184,8 @@ protected void parseJSONMember(JsonObject.Member member) { } } - public void uploadPart(String partId, byte[] bytes, long offset, Long partSize, long totalSizeOfFile) - throws MalformedURLException, NoSuchAlgorithmException { + public void uploadPart(String partId, InputStream stream, long offset, long partSize, long totalSizeOfFile) + throws IOException, NoSuchAlgorithmException { URL uploadPartURL = this.sessionInfo.getSessionEndpoints().getUploadPartEndpoint(); @@ -191,6 +193,9 @@ public void uploadPart(String partId, byte[] bytes, long offset, Long partSize, request.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_OCTET_STREAM); request.addHeader(HttpHeaders.X_BOX_PART_ID, partId); + byte[] bytes = new byte[(int) partSize]; + stream.read(bytes); + byte[] digestBytes = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1).digest(bytes); String digest = Base64.encode(digestBytes); request.addHeader(HttpHeaders.DIGEST, DIGEST_HEADER_PREFIX_SHA + digest); diff --git a/src/main/java/com/box/sdk/LargeFileUpload.java b/src/main/java/com/box/sdk/LargeFileUpload.java index 159c52687..c6f3a77a3 100644 --- a/src/main/java/com/box/sdk/LargeFileUpload.java +++ b/src/main/java/com/box/sdk/LargeFileUpload.java @@ -124,12 +124,10 @@ private static void uploadPart(BoxFileUploadSession session, InputStream stream, long partSize, long fileSize) throws Exception { String partId = generateHex(); - byte[] bytes = new byte[(int) partSize]; - stream.read(bytes); for(int i = 0; i < 3; i++) { try { - session.uploadPart(partId, bytes, offset, partSize, fileSize); + session.uploadPart(partId, stream, offset, partSize, fileSize); break; } catch (BoxAPIException ex) { if (i == 2) { diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index d073c6e37..c6a0620fd 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -1093,10 +1093,7 @@ private MessageDigest uploadParts(BoxFile uploadedFile, BoxFileUploadSession.Inf canBreak = true; } - bytes = new byte[(int) min]; - dis.read(bytes); - - session.getResource().uploadPart(generateHex(), bytes, offset, min, fileSize); + session.getResource().uploadPart(generateHex(), dis, offset, min, fileSize); offset = offset + session.getPartSize(); processed += min; @@ -1213,4 +1210,38 @@ private static byte[] readAllBytes(String fileName) throws IOException { f.read(b); return b; } + + @Test + @Category(IntegrationTest.class) + public void uploadLargeFile() throws Exception { + File file = new File("/Users/kshanmugasundaram/Downloads/tenmb"); + FileInputStream stream = new FileInputStream(file); + + BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); + BoxFolder rootFolder = BoxFolder.getRootFolder(api); + BoxFile.Info fileUploaded = rootFolder.uploadLargeFile(stream, "tenmb", file.length()); + Assert.assertNotNull(fileUploaded); + + fileUploaded.getResource().delete(); + } + + @Test + @Category(IntegrationTest.class) + public void uploadLargeFileVersion() throws Exception { + String fileName = "Tamme-Lauri_tamm_suvepäeval.jpg"; + URL fileURL = this.getClass().getResource("/sample-files/" + fileName); + String filePath = URLDecoder.decode(fileURL.getFile(), "utf-8"); + File file = new File(filePath); + FileInputStream stream = new FileInputStream(file); + + BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); + BoxFolder rootFolder = BoxFolder.getRootFolder(api); + BoxFile.Info uploadedFile = rootFolder.uploadFile(stream, "tenmb"); + + stream = new FileInputStream(file); + BoxFile.Info fileVerion = uploadedFile.getResource().uploadLargeFile(stream, file.length()); + Assert.assertNotNull(fileVerion); + + fileVerion.getResource().delete(); + } } From 10283ac64a1cea70916a9d59f50b232da709e032 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Wed, 1 Mar 2017 16:01:42 -0800 Subject: [PATCH 068/119] Checkstyle clean up --- .../java/com/box/sdk/BoxFileUploadSession.java | 7 ++++--- src/main/java/com/box/sdk/LargeFileUpload.java | 14 ++++---------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index f04509f8d..42fe7acb2 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -242,11 +242,12 @@ public BoxFile.Info commit(String digest, List parts, String body = this.getCommitBody(parts, attributes); request.setBody(body); - BoxAPIResponse response = (BoxAPIResponse) request.send(); + BoxAPIResponse response = request.send(); if (response instanceof BoxJSONResponse) { - return getFile((BoxJSONResponse) response); + return this.getFile((BoxJSONResponse) response); } else { - throw new BoxAPIException("Commit response content type is not application/json. The response code : " + response.getResponseCode()); + throw new BoxAPIException("Commit response content type is not application/json. The response code : " + + response.getResponseCode()); } } diff --git a/src/main/java/com/box/sdk/LargeFileUpload.java b/src/main/java/com/box/sdk/LargeFileUpload.java index c6f3a77a3..c76d93397 100644 --- a/src/main/java/com/box/sdk/LargeFileUpload.java +++ b/src/main/java/com/box/sdk/LargeFileUpload.java @@ -1,20 +1,14 @@ package com.box.sdk; -import com.box.sdk.*; -import com.box.sdk.http.ContentType; -import com.box.sdk.http.HttpHeaders; -import com.box.sdk.http.HttpMethod; -import com.eclipsesource.json.JsonObject; - -import java.io.ByteArrayInputStream; import java.io.InputStream; -import java.net.MalformedURLException; import java.net.URL; import java.security.DigestInputStream; import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.Random; +import com.box.sdk.http.HttpMethod; +import com.eclipsesource.json.JsonObject; + /** * */ @@ -125,7 +119,7 @@ private static void uploadPart(BoxFileUploadSession session, InputStream stream, String partId = generateHex(); - for(int i = 0; i < 3; i++) { + for (int i = 0; i < 3; i++) { try { session.uploadPart(partId, stream, offset, partSize, fileSize); break; From c32f37ab57edc752e88df9f6c2053b90ed1bfae8 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Thu, 2 Mar 2017 10:03:24 -0800 Subject: [PATCH 069/119] Upload part signature change and 202 response code fix --- src/main/java/com/box/sdk/BoxAPIResponse.java | 9 +++++ .../com/box/sdk/BoxFileUploadSession.java | 25 ++++++++++++-- .../java/com/box/sdk/LargeFileUpload.java | 33 +++++++++++-------- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxAPIResponse.java b/src/main/java/com/box/sdk/BoxAPIResponse.java index 92f376661..424ae94b5 100644 --- a/src/main/java/com/box/sdk/BoxAPIResponse.java +++ b/src/main/java/com/box/sdk/BoxAPIResponse.java @@ -89,6 +89,15 @@ public long getContentLength() { return this.connection.getContentLength(); } + /** + * Gets the value of the given header field. + * @param fieldName name of the header field. + * @return value of the header. + */ + public String getHeaderField(String fieldName) { + return this.connection.getHeaderField(fieldName); + } + /** * Gets an InputStream for reading this response's body. * @return an InputStream for reading the response's body. diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index 42fe7acb2..05ab31a35 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -184,8 +184,8 @@ protected void parseJSONMember(JsonObject.Member member) { } } - public void uploadPart(String partId, InputStream stream, long offset, long partSize, long totalSizeOfFile) - throws IOException, NoSuchAlgorithmException { + public BoxFileUploadSessionPart uploadPart(String partId, InputStream stream, long offset, long partSize, + long totalSizeOfFile) throws IOException, NoSuchAlgorithmException { URL uploadPartURL = this.sessionInfo.getSessionEndpoints().getUploadPartEndpoint(); @@ -204,6 +204,13 @@ public void uploadPart(String partId, InputStream stream, long offset, long part request.setBody(new ByteArrayInputStream(bytes)); request.send(); + + BoxFileUploadSessionPart part = new BoxFileUploadSessionPart(); + part.setPartId(partId); + part.setOffset(offset); + part.setSize(partSize); + + return part; } public BoxFileUploadSessionPartList listParts(int marker, int limit) { @@ -243,6 +250,19 @@ public BoxFile.Info commit(String digest, List parts, request.setBody(body); BoxAPIResponse response = request.send(); + if (response.getResponseCode() == 202) { + String retryInterval = response.getHeaderField("retry-after"); + if (retryInterval != null) { + try { + Thread.sleep(new Integer(retryInterval) * 1000); + } catch (InterruptedException ie) { + throw new BoxAPIException("Commit retry failed. ", ie); + } + + return this.commit(digest, parts, attributes, ifMatch, ifNoneMatch); + } + } + if (response instanceof BoxJSONResponse) { return this.getFile((BoxJSONResponse) response); } else { @@ -253,7 +273,6 @@ public BoxFile.Info commit(String digest, List parts, private BoxFile.Info getFile(BoxJSONResponse response) { JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); - System.out.println("Response: " + response.getResponseCode() + " : " + response.getJSON()); JsonArray array = (JsonArray) jsonObject.get("entries"); JsonObject fileObj = (JsonObject) array.get(0); diff --git a/src/main/java/com/box/sdk/LargeFileUpload.java b/src/main/java/com/box/sdk/LargeFileUpload.java index c76d93397..8024cf749 100644 --- a/src/main/java/com/box/sdk/LargeFileUpload.java +++ b/src/main/java/com/box/sdk/LargeFileUpload.java @@ -4,6 +4,8 @@ import java.net.URL; import java.security.DigestInputStream; import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.List; import java.util.Random; import com.box.sdk.http.HttpMethod; @@ -28,13 +30,15 @@ static BoxFile.Info upload(BoxAPIConnection boxApi, String folderId, InputStream BoxFileUploadSession.Info session = createUploadSession(boxApi, folderId, url, fileName, fileSize); - MessageDigest digest = uploadParts(session, stream, fileSize); + MessageDigest digest = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1); + List parts = uploadParts(session, stream, fileSize, digest); + byte[] digestBytes = digest.digest(); String digestStr = Base64.encode(digestBytes); BoxFileUploadSessionPartList list = session.getResource().listParts(0, 1000); try { - return session.getResource().commit(digestStr, list.getParts(), null, null, null); + return session.getResource().commit(digestStr, parts, null, null, null); } finally { session.getResource().abortUploadSession(); } @@ -63,13 +67,15 @@ private static BoxFileUploadSession.Info createUploadSession(BoxAPIConnection bo static BoxFile.Info upload(BoxAPIConnection boxApi, InputStream stream, URL url, long fileSize) throws Exception { BoxFileUploadSession.Info session = createUploadSession(boxApi, url, fileSize); - MessageDigest digest = uploadParts(session, stream, fileSize); + MessageDigest digest = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1); + List parts = uploadParts(session, stream, fileSize, digest); + byte[] digestBytes = digest.digest(); String digestStr = Base64.encode(digestBytes); BoxFileUploadSessionPartList list = session.getResource().listParts(0, 1000); try { - return session.getResource().commit(digestStr, list.getParts(), null, null, null); + return session.getResource().commit(digestStr, parts, null, null, null); } finally { session.getResource().abortUploadSession(); } @@ -90,11 +96,11 @@ private static BoxFileUploadSession.Info createUploadSession(BoxAPIConnection bo return session.new Info(jsonObject); } - private static MessageDigest uploadParts(BoxFileUploadSession.Info session, InputStream stream, long fileSize) - throws Exception { + private static List uploadParts(BoxFileUploadSession.Info session, InputStream stream, + long fileSize, MessageDigest digest) throws Exception { - MessageDigest digest = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1); DigestInputStream dis = new DigestInputStream(stream, digest); + List parts = new ArrayList(); long partSize = session.getPartSize(); long offset = 0; @@ -105,30 +111,32 @@ private static MessageDigest uploadParts(BoxFileUploadSession.Info session, Inpu partSize = diff; } - uploadPart(session.getResource(), dis, offset, partSize, fileSize); + BoxFileUploadSessionPart part = uploadPart(session.getResource(), dis, offset, partSize, fileSize); + parts.add(part); processed += partSize; offset += partSize; } - return digest; + return parts; } - private static void uploadPart(BoxFileUploadSession session, InputStream stream, long offset, + private static BoxFileUploadSessionPart uploadPart(BoxFileUploadSession session, InputStream stream, long offset, long partSize, long fileSize) throws Exception { String partId = generateHex(); for (int i = 0; i < 3; i++) { try { - session.uploadPart(partId, stream, offset, partSize, fileSize); - break; + return session.uploadPart(partId, stream, offset, partSize, fileSize); } catch (BoxAPIException ex) { if (i == 2) { throw ex; } } } + + return null; } public static String generateHex() { @@ -141,5 +149,4 @@ public static String generateHex() { return hex; } - } From 6e839c93042f20ad129225ac016100b870c06785 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Thu, 2 Mar 2017 13:25:38 -0800 Subject: [PATCH 070/119] Added Javadoc --- src/main/java/com/box/sdk/BoxAPIRequest.java | 6 + src/main/java/com/box/sdk/BoxFile.java | 14 +- .../com/box/sdk/BoxFileUploadSession.java | 161 ++++++++++++++---- .../com/box/sdk/BoxFileUploadSessionPart.java | 37 ++-- .../box/sdk/BoxFileUploadSessionPartList.java | 15 +- src/main/java/com/box/sdk/BoxFolder.java | 16 +- .../java/com/box/sdk/LargeFileUpload.java | 105 +++++++++++- .../java/com/box/sdk/http/ContentType.java | 9 +- .../java/com/box/sdk/http/HttpHeaders.java | 31 +++- .../java/com/box/sdk/http/HttpMethod.java | 41 ++++- 10 files changed, 379 insertions(+), 56 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxAPIRequest.java b/src/main/java/com/box/sdk/BoxAPIRequest.java index 62eb7cc6f..036da1671 100644 --- a/src/main/java/com/box/sdk/BoxAPIRequest.java +++ b/src/main/java/com/box/sdk/BoxAPIRequest.java @@ -78,6 +78,12 @@ public BoxAPIRequest(BoxAPIConnection api, URL url, String method) { this.addHeader("Accept-Charset", "utf-8"); } + /** + * 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 method the HTTP method of the request. + */ public BoxAPIRequest(BoxAPIConnection api, URL uploadPartEndpoint, HttpMethod method) { this(api, uploadPartEndpoint, method.name()); } diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index b1bf9bf36..b49814677 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -861,6 +861,12 @@ public BoxFile.Info setCollections(BoxCollection... collections) { return new Info(jsonObject); } + /** + * Creates an upload session to create a new version of a file in chunks. + * This will first verify that the version can be created and then open a session for uploading pieces of the file. + * @param fileSize the size of the file that will be uploaded. + * @return the created upload session instance. + */ public BoxFileUploadSession.Info createUploadSession(long fileSize) { URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), this.getID()); @@ -879,7 +885,13 @@ public BoxFileUploadSession.Info createUploadSession(long fileSize) { return session.new Info(jsonObject); } - public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize) throws Exception { + /** + * Creates a new version of a file. + * @param inputStream the stream instance that contains the data. + * @param fileSize the size of the file that will be uploaded. + * @return the created file instance. + */ + public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize) { URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), this.getID()); return LargeFileUpload.upload(this.getAPI(), inputStream, url, fileSize); diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index 05ab31a35..995c5268f 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -19,6 +19,12 @@ import com.eclipsesource.json.JsonObject; import com.eclipsesource.json.JsonValue; +/** + * This API provides a way to reliably upload larger files to Box by chunking them into a sequence of parts. + * When using this APIinstead of the single file upload API, a request failure means a client only needs to + * retry upload of a single part instead of the entire file. Parts can also be uploaded in parallel allowing + * for potential performance improvement. + */ @BoxResourceType("upload-session") public class BoxFileUploadSession extends BoxResource { @@ -30,12 +36,20 @@ public class BoxFileUploadSession extends BoxResource { private Info sessionInfo; + /** + * Constructs a BoxFileUploadSession for a file with a given ID. + * @param api the API connection to be used by the upload session. + * @param id the ID of the upload session. + */ BoxFileUploadSession(BoxAPIConnection api, String id) { super(api, id); } + /** + * Model contains the upload session information. + */ public class Info extends BoxResource.Info { - private BoxAPIConnection api; + private Date sessionExpiresAt; private String uploadSessionId; private Endpoints sessionEndpoints; @@ -43,23 +57,6 @@ public class Info extends BoxResource.Info { private int totalParts; private int partsProcessed; - /** - * Constructs an empty Info object. - */ - public Info() { - super(); - BoxFileUploadSession.this.sessionInfo = this; - } - - /** - * Constructs an Info object by parsing information from a JSON string. - * @param json the JSON string to parse. - */ - public Info(String json) { - super(json); - BoxFileUploadSession.this.sessionInfo = this; - } - /** * Constructs an Info object using an already parsed JSON object. * @param jsonObject the parsed JSON object. @@ -69,42 +66,63 @@ public Info(String json) { BoxFileUploadSession.this.sessionInfo = this; } - private BoxAPIConnection getAPI() { - return this.api; - } - - private void setAPI(BoxAPIConnection api) { - this.api = api; - } - + /** + * Returns the BoxFileUploadSession isntance to which this object belongs to. + * @return the instance of upload session. + */ public BoxFileUploadSession getResource() { return BoxFileUploadSession.this; } + /** + * Returns the total parts of the file that is uploaded in the upload session. + * @return the total number of parts. + */ public int getTotalParts() { return this.totalParts; } + /** + * Returns the parts that are processed so for. + * @return the number of the processed parts. + */ public int getPartsProcessed() { return this.partsProcessed; } + /** + * Returns the date and time at which the upload session expires. + * @return the date and time in UTC format. + */ public Date getSessionExpiresAt() { return this.sessionExpiresAt; } + /** + * Returns the upload session id. + * @return the id string. + */ public String getUploadSessionId() { return this.uploadSessionId; } + /** + * Returns the session endpoints that can be called for this upload session. + * @return the Endpoints instance. + */ public Endpoints getSessionEndpoints() { return this.sessionEndpoints; } + /** + * Returns the size of the each part. Only the last part of the file can be lessor than this value. + * @return the part size. + */ public long getPartSize() { return this.partSize; } + @Override protected void parseJSONMember(JsonObject.Member member) { String memberName = member.getName(); @@ -130,6 +148,9 @@ protected void parseJSONMember(JsonObject.Member member) { } } + /** + * Represents the end points specific to an upload session. + */ public class Endpoints extends BoxJSONObject { private URL listPartsEndpoint; private URL commitEndpoint; @@ -137,26 +158,50 @@ public class Endpoints extends BoxJSONObject { private URL statusEndpoint; private URL abortEndpoint; + /** + * Constructs an Endpoints object using an already parsed JSON object. + * @param jsonObject the parsed JSON object. + */ Endpoints(JsonObject jsonObject) { super(jsonObject); } + /** + * Returns the list parts end point. + * @return the url of the list parts end point. + */ public URL getListPartsEndpoint() { return this.listPartsEndpoint; } + /** + * Returns the commit end point. + * @return the url of the commit end point. + */ public URL getCommitEndpoint() { return this.commitEndpoint; } + /** + * Returns the upload part end point. + * @return the url of the upload part end point. + */ public URL getUploadPartEndpoint() { return this.uploadPartEndpoint; } + /** + * Returns the upload session status end point. + * @return the url of the session end point. + */ public URL getStatusEndpoint() { return this.statusEndpoint; } + /** + * Returns the abort upload session end point. + * @return the url of the abort end point. + */ public URL getAbortEndpoint() { return this.abortEndpoint; } @@ -184,8 +229,18 @@ protected void parseJSONMember(JsonObject.Member member) { } } + /** + * Uploads a chunk of a file to an open upload session. + * @param partId a unique 8 character hex number that identifies the part. + * @param stream the stream that is used to read the chunck using the offset and part size. + * @param offset the byte position where the chunk begins in the file. + * @param partSize the part size returned as part of the upload session instance creation. + * Only the last chunk can have a lesser value. + * @param totalSizeOfFile The total size of the file being uploaded. + * @return the part instance that contains the part id, offset and part size. + */ public BoxFileUploadSessionPart uploadPart(String partId, InputStream stream, long offset, long partSize, - long totalSizeOfFile) throws IOException, NoSuchAlgorithmException { + long totalSizeOfFile) { URL uploadPartURL = this.sessionInfo.getSessionEndpoints().getUploadPartEndpoint(); @@ -193,15 +248,30 @@ public BoxFileUploadSessionPart uploadPart(String partId, InputStream stream, lo request.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_OCTET_STREAM); request.addHeader(HttpHeaders.X_BOX_PART_ID, partId); + //Read the partSize bytes from the stream byte[] bytes = new byte[(int) partSize]; - stream.read(bytes); + try { + stream.read(bytes); + } catch (IOException ioe) { + throw new BoxAPIException("Reading data from stream failed.", ioe); + } + + MessageDigest digestInstance = null; + try { + digestInstance = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1); + } catch (NoSuchAlgorithmException ae) { + throw new BoxAPIException("Digest algorithm not found", ae); + } - byte[] digestBytes = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1).digest(bytes); + //Creates the digest using SHA1 algorithm. Then encodes the bytes using Base64. + byte[] digestBytes = digestInstance.digest(bytes); String digest = Base64.encode(digestBytes); request.addHeader(HttpHeaders.DIGEST, DIGEST_HEADER_PREFIX_SHA + digest); + //Content-Range: bytes offset-part/totalSize request.addHeader(HttpHeaders.CONTENT_RANGE, "bytes " + offset + "-" + (offset + partSize - 1) + "/" + totalSizeOfFile); + //Creates the body request.setBody(new ByteArrayInputStream(bytes)); request.send(); @@ -213,6 +283,12 @@ public BoxFileUploadSessionPart uploadPart(String partId, InputStream stream, lo return part; } + /** + * Returns a list of all parts that have been uploaded to an upload session. + * @param marker paging marker for the list of parts. + * @param limit maximum number of parts to return. + * @return the list of parts. + */ public BoxFileUploadSessionPartList listParts(int marker, int limit) { URL listPartsURL = this.sessionInfo.getSessionEndpoints().getListPartsEndpoint(); URLTemplate template = new URLTemplate(listPartsURL.toString()); @@ -221,6 +297,7 @@ public BoxFileUploadSessionPartList listParts(int marker, int limit) { .appendParam(MARKER_QUERY_STRING, marker) .appendParam(LIMIT_QUERY_STRING, limit) .toString(); + //Template is initalized with the full URL. So empty string for the path. URL url = template.buildWithQuery("", queryString); BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, HttpMethod.GET); @@ -230,6 +307,15 @@ public BoxFileUploadSessionPartList listParts(int marker, int limit) { return new BoxFileUploadSessionPartList(jsonObject); } + /** + * Commit an upload session after all parts have been uploaded, creating the new file or the version. + * @param digest the base64-encoded SHA-1 hash of the file being uploaded. + * @param parts the list of uploaded parts to be committed. + * @param attributes the key value pairs of attributes from the file instance. + * @param ifMatch ensures that your app only alters files/folders on Box if you have the current version. + * @param ifNoneMatch ensure that it retrieve unnecessary data if the most current version of file is on-hand. + * @return the created file instance. + */ public BoxFile.Info commit(String digest, List parts, Map attributes, String ifMatch, String ifNoneMatch) { @@ -246,10 +332,12 @@ public BoxFile.Info commit(String digest, List parts, request.addHeader(HttpHeaders.IF_NONE_MATCH, ifNoneMatch); } + //Creates the body of the request String body = this.getCommitBody(parts, attributes); request.setBody(body); BoxAPIResponse response = request.send(); + //Retry the commit operation after the given number of seconds if the HTTP response code is 202. if (response.getResponseCode() == 202) { String retryInterval = response.getHeaderField("retry-after"); if (retryInterval != null) { @@ -264,6 +352,7 @@ public BoxFile.Info commit(String digest, List parts, } if (response instanceof BoxJSONResponse) { + //Create the file instance from the response return this.getFile((BoxJSONResponse) response); } else { throw new BoxAPIException("Commit response content type is not application/json. The response code : " @@ -271,6 +360,9 @@ public BoxFile.Info commit(String digest, List parts, } } + /* + * Creates the file isntance from the JSON body of the response. + */ private BoxFile.Info getFile(BoxJSONResponse response) { JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); @@ -282,6 +374,9 @@ private BoxFile.Info getFile(BoxJSONResponse response) { return file.new Info(fileObj); } + /* + * Creates the JSON body for the commit request. + */ private String getCommitBody(List parts, Map attributes) { JsonObject jsonObject = new JsonObject(); @@ -307,6 +402,11 @@ private String getCommitBody(List parts, Map getParts() { return this.parts; } + /** + * Returns the paging marker for the list of parts. + * @return the paging marker. + */ public int getMarker() { return this.marker; } + @Override protected void parseJSONMember(JsonObject.Member member) { String memberName = member.getName(); JsonValue value = member.getValue(); @@ -45,6 +55,9 @@ protected void parseJSONMember(JsonObject.Member member) { } } + /* + * Creates List of parts from the JSON array + */ private List getParts(JsonArray partsArray) { List parts = new ArrayList(); for (JsonValue value: partsArray) { diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index cf49647d3..68b7a9467 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -729,6 +729,13 @@ public void deleteMetadata(String templateName, String scope) { response.disconnect(); } + /** + * Creates an upload session to create a new file in chunks. + * This will first verify that the file can be created and then open a session for uploading pieces of the file. + * @param fileName the name of the file to be created + * @param fileSize the size of the file that will be uploaded + * @return the created upload session instance + */ public BoxFileUploadSession.Info createUploadSession(String fileName, long fileSize) { URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL()); @@ -749,7 +756,14 @@ public BoxFileUploadSession.Info createUploadSession(String fileName, long fileS return session.new Info(jsonObject); } - public BoxFile.Info uploadLargeFile(InputStream inputStream, String fileName, long fileSize) throws Exception { + /** + * Creates a new file. + * @param inputStream the stream instance that contains the data. + * @param fileName the name of the file to be created. + * @param fileSize the size of the file that will be uploaded. + * @return the created file instance. + */ + public BoxFile.Info uploadLargeFile(InputStream inputStream, String fileName, long fileSize) { URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL()); return LargeFileUpload.upload(this.getAPI(), this.getID(), inputStream, url, fileName, fileSize); diff --git a/src/main/java/com/box/sdk/LargeFileUpload.java b/src/main/java/com/box/sdk/LargeFileUpload.java index 8024cf749..44fd9bce7 100644 --- a/src/main/java/com/box/sdk/LargeFileUpload.java +++ b/src/main/java/com/box/sdk/LargeFileUpload.java @@ -1,9 +1,11 @@ package com.box.sdk; +import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.security.DigestInputStream; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -12,7 +14,7 @@ import com.eclipsesource.json.JsonObject; /** - * + * Utility class for uploading large files. */ public final class LargeFileUpload { @@ -25,18 +27,37 @@ public final class LargeFileUpload { private LargeFileUpload() { } + /** + * Uploads a new large file. + * @param boxApi the API connection to be used by the upload session. + * @param folderId the id of the folder in which the file will be uploaded. + * @param stream the input stream that feeds the content of the file. + * @param url the upload session URL. + * @param fileName the name of the file to be created. + * @param fileSize the total size of the file. + * @return the created file instance. + */ static BoxFile.Info upload(BoxAPIConnection boxApi, String folderId, InputStream stream, URL url, - String fileName, long fileSize) throws Exception { + String fileName, long fileSize) { + //Create a upload session BoxFileUploadSession.Info session = createUploadSession(boxApi, folderId, url, fileName, fileSize); - MessageDigest digest = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1); + MessageDigest digest = null; + try { + digest = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1); + } catch (NoSuchAlgorithmException ae) { + throw new BoxAPIException("Digest algorithm not found", ae); + } + + //Upload parts using the upload session List parts = uploadParts(session, stream, fileSize, digest); + //Creates the file hash byte[] digestBytes = digest.digest(); String digestStr = Base64.encode(digestBytes); - BoxFileUploadSessionPartList list = session.getResource().listParts(0, 1000); + //Commit the upload session. If there is a failure, abort the commit. try { return session.getResource().commit(digestStr, parts, null, null, null); } finally { @@ -49,6 +70,7 @@ private static BoxFileUploadSession.Info createUploadSession(BoxAPIConnection bo BoxJSONRequest request = new BoxJSONRequest(boxApi, url, HttpMethod.POST); + //Create the JSON body of the request JsonObject body = new JsonObject(); body.add("folder_id", folderId); body.add("file_name", fileName); @@ -64,15 +86,34 @@ private static BoxFileUploadSession.Info createUploadSession(BoxAPIConnection bo return session.new Info(jsonObject); } - static BoxFile.Info upload(BoxAPIConnection boxApi, InputStream stream, URL url, long fileSize) throws Exception { + /** + * Creates a new version of a large file. + * @param boxApi the API connection to be used by the upload session. + * @param stream the input stream that feeds the content of the file. + * @param url the upload session URL. + * @param fileSize the total size of the file. + * @return the file instance that also contains the version information. + */ + static BoxFile.Info upload(BoxAPIConnection boxApi, InputStream stream, URL url, long fileSize) { + + //creates a upload session BoxFileUploadSession.Info session = createUploadSession(boxApi, url, fileSize); - MessageDigest digest = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1); + MessageDigest digest = null; + try { + digest = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1); + } catch (NoSuchAlgorithmException ae) { + throw new BoxAPIException("Digest algorithm not found", ae); + } + + //Upload parts using the upload session List parts = uploadParts(session, stream, fileSize, digest); + //Creates the file hash byte[] digestBytes = digest.digest(); String digestStr = Base64.encode(digestBytes); + //Commit the upload session. If there is a failure, abort the commit. BoxFileUploadSessionPartList list = session.getResource().listParts(0, 1000); try { return session.getResource().commit(digestStr, parts, null, null, null); @@ -83,6 +124,8 @@ static BoxFile.Info upload(BoxAPIConnection boxApi, InputStream stream, URL url, private static BoxFileUploadSession.Info createUploadSession(BoxAPIConnection boxApi, URL url, long fileSize) { BoxJSONRequest request = new BoxJSONRequest(boxApi, url, HttpMethod.POST); + + //Creates the body of the request JsonObject body = new JsonObject(); body.add("file_size", fileSize); request.setBody(body.toString()); @@ -96,8 +139,11 @@ private static BoxFileUploadSession.Info createUploadSession(BoxAPIConnection bo return session.new Info(jsonObject); } + /* + * Upload parts of the file. The part size is retrieved from the upload session. + */ private static List uploadParts(BoxFileUploadSession.Info session, InputStream stream, - long fileSize, MessageDigest digest) throws Exception { + long fileSize, MessageDigest digest) { DigestInputStream dis = new DigestInputStream(stream, digest); List parts = new ArrayList(); @@ -107,13 +153,16 @@ private static List uploadParts(BoxFileUploadSession.I long processed = 0; while (processed < fileSize) { long diff = fileSize - processed; + //The size last part of the file can be lesser than the part size. if (diff < partSize) { partSize = diff; } + //Upload a part BoxFileUploadSessionPart part = uploadPart(session.getResource(), dis, offset, partSize, fileSize); parts.add(part); + //Increase the offset and proceesed bytes to calculate the Content-Range header. processed += partSize; offset += partSize; } @@ -121,11 +170,15 @@ private static List uploadParts(BoxFileUploadSession.I return parts; } + /* + * Uploads the part of the file. + */ private static BoxFileUploadSessionPart uploadPart(BoxFileUploadSession session, InputStream stream, long offset, - long partSize, long fileSize) throws Exception { + long partSize, long fileSize) { String partId = generateHex(); + //Retries the upload part 3 times in case of failure. for (int i = 0; i < 3; i++) { try { return session.uploadPart(partId, stream, offset, partSize, fileSize); @@ -136,9 +189,13 @@ private static BoxFileUploadSessionPart uploadPart(BoxFileUploadSession session, } } - return null; + throw new BoxAPIException("Upload part failed for offset: " + offset + " range: " + partSize); } + /** + * Generates a 8 character random hex value. + * @return the hex string. + */ public static String generateHex() { String hex = ""; while (hex.length() != 8) { @@ -149,4 +206,34 @@ public static String generateHex() { return hex; } + + /** + * Generates the Base64 encoded SHA-1 hash for content available in the stream. + * It can be used to calculate the hash of a file. + * @param stream the input stream of the file or data. + * @return the Base64 encoded hash string. + */ + public static String generateDigest(InputStream stream) { + MessageDigest digest = null; + try { + digest = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1); + } catch (NoSuchAlgorithmException ae) { + throw new BoxAPIException("Digest algorithm not found", ae); + } + + //Calcuate the digest using the stream. + DigestInputStream dis = new DigestInputStream(stream, digest); + try { + int value = dis.read(); + while (value != -1) { + value = dis.read(); + } + } catch (IOException ioe) { + throw new BoxAPIException("Reading the stream failed.", ioe); + } + + //Get the calculated digest for the stream + byte[] digestBytes = digest.digest(); + return Base64.encode(digestBytes); + } } diff --git a/src/main/java/com/box/sdk/http/ContentType.java b/src/main/java/com/box/sdk/http/ContentType.java index c16312e1d..1106cc26b 100644 --- a/src/main/java/com/box/sdk/http/ContentType.java +++ b/src/main/java/com/box/sdk/http/ContentType.java @@ -1,11 +1,18 @@ package com.box.sdk.http; /** - * + * HTTP Content-Type constants. */ public final class ContentType { + /** + * It is used when the HTTP request content type is application/json. + */ public static final String APPLICATION_JSON = "application/json"; + + /** + * It is used when the HTTP request content type is application/octet-stream. + */ public static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; //Prevents instantiation diff --git a/src/main/java/com/box/sdk/http/HttpHeaders.java b/src/main/java/com/box/sdk/http/HttpHeaders.java index f9b151afa..aac67afeb 100644 --- a/src/main/java/com/box/sdk/http/HttpHeaders.java +++ b/src/main/java/com/box/sdk/http/HttpHeaders.java @@ -1,16 +1,43 @@ package com.box.sdk.http; /** - * + * HTTP header Key constants. */ public final class HttpHeaders { + /** + * HTTP header key Content-Length. + */ public static final String CONTENT_LENGTH = "Content-Length"; - public static final String CONTENT_RANGE = "Content-Range"; + + /** + * HTTP header key Content-Type. + */ public static final String CONTENT_TYPE = "Content-Type"; + + /** + * HTTP header key Content-Range. + */ + public static final String CONTENT_RANGE = "Content-Range"; + + /** + * HTTP header key DIgest. + */ public static final String DIGEST = "Digest"; + + /** + * HTTP header key If-Match. + */ public static final String IF_MATCH = "If-Match"; + + /** + * HTTP header key If-None-Match. + */ public static final String IF_NONE_MATCH = "If-None-Match"; + + /** + * HTTP header key X-Box-Part-Id. + */ public static final String X_BOX_PART_ID = "X-Box-Part-Id"; //Prevents instantiation diff --git a/src/main/java/com/box/sdk/http/HttpMethod.java b/src/main/java/com/box/sdk/http/HttpMethod.java index 062b94f54..8c08ff287 100644 --- a/src/main/java/com/box/sdk/http/HttpMethod.java +++ b/src/main/java/com/box/sdk/http/HttpMethod.java @@ -1,9 +1,46 @@ package com.box.sdk.http; /** - * + * HTTP method constants. */ public enum HttpMethod { + /** + * HTTP GET method. + */ + GET, - GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; + /** + * HTTP HEAD method. + */ + HEAD, + + /** + * HTTP POST method. + */ + POST, + + /** + * HTTP PUT method. + */ + PUT, + + /** + * HTTP PATCH method. + */ + PATCH, + + /** + * HTTP DELETE method. + */ + DELETE, + + /** + * HTTP OPTIONS method. + */ + OPTIONS, + + /** + * HTTP TRACE method. + */ + TRACE; } From 0273dbaa0686b58bf9b3434a731f77ea9add003e Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Thu, 2 Mar 2017 13:31:05 -0800 Subject: [PATCH 071/119] coding style changes --- src/test/java/com/box/sdk/BoxFileTest.java | 28 ++++++++++---------- src/test/java/com/box/sdk/BoxFolderTest.java | 6 ++--- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index c6a0620fd..3c7da109a 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -985,19 +985,19 @@ public void uploadSessionCommitFlowSuccess() throws Exception { long fileSize = file.length(); //Create the session - BoxFileUploadSession.Info session = createFileUploadSession(rootFolder, fileName, fileSize); + BoxFileUploadSession.Info session = this.createFileUploadSession(rootFolder, fileName, fileSize); //Create the parts - MessageDigest fileDigest = uploadParts(uploadedFile, session, fileSize); + MessageDigest fileDigest = this.uploadParts(uploadedFile, session, fileSize); //List the session parts - List parts = listUploadSessionParts(session.getResource()); + List parts = this.listUploadSessionParts(session.getResource()); byte[] digestBytes = fileDigest.digest(); String digest = Base64.encode(digestBytes); //Verify the delete session - uploadedFile = commitSession(session.getResource(), digest, parts); + uploadedFile = this.commitSession(session.getResource(), digest, parts); } finally { if (uploadedFile != null) { uploadedFile.delete(); @@ -1029,24 +1029,24 @@ public void uploadSessionVersionCommitFlowSuccess() throws Exception { BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); BoxFolder rootFolder = BoxFolder.getRootFolder(api); - BoxFile.Info imageFileInfo = createImageFile(rootFolder); + BoxFile.Info imageFileInfo = this.createImageFile(rootFolder); BoxFile uploadedFile = imageFileInfo.getResource(); try { //Create the session - BoxFileUploadSession.Info session = createFileUploadSession(uploadedFile, imageFileInfo.getSize()); + BoxFileUploadSession.Info session = this.createFileUploadSession(uploadedFile, imageFileInfo.getSize()); //Create the parts - MessageDigest fileDigest = uploadParts(uploadedFile, session, imageFileInfo.getSize()); + MessageDigest fileDigest = this.uploadParts(uploadedFile, session, imageFileInfo.getSize()); //List the session parts - List parts = listUploadSessionParts(session.getResource()); + List parts = this.listUploadSessionParts(session.getResource()); byte[] digestBytes = fileDigest.digest(); String digest = Base64.encode(digestBytes); //Verify the delete session - uploadedFile = commitSession(session.getResource(), digest, parts); + uploadedFile = this.commitSession(session.getResource(), digest, parts); } finally { uploadedFile.delete(); } @@ -1085,7 +1085,7 @@ private MessageDigest uploadParts(BoxFile uploadedFile, BoxFileUploadSession.Inf byte[] bytes = null; long processed = 0; boolean canBreak = false; - while(true) { + while (true) { long min = session.getPartSize(); long diff = fileSize - processed; if (diff < min) { @@ -1093,7 +1093,7 @@ private MessageDigest uploadParts(BoxFile uploadedFile, BoxFileUploadSession.Inf canBreak = true; } - session.getResource().uploadPart(generateHex(), dis, offset, min, fileSize); + session.getResource().uploadPart(this.generateHex(), dis, offset, min, fileSize); offset = offset + session.getPartSize(); processed += min; @@ -1158,10 +1158,10 @@ public void uploadSessionAbortFlowSuccess() throws Exception { Assert.assertNotNull(endpoints.getAbortEndpoint()); //Verify the status of the session - getUploadSessionStatus(session.getResource()); + this.getUploadSessionStatus(session.getResource()); //Verify the delete session - abortUploadSession(session.getResource()); + this.abortUploadSession(session.getResource()); } finally { uploadedFile.delete(); } @@ -1199,7 +1199,7 @@ private void abortUploadSession(BoxFileUploadSession session) { //If the session is aborted, this line should not be executed. Assert.assertFalse("Upload session is not deleted", true); - } catch(BoxAPIException apiEx) { + } catch (BoxAPIException apiEx) { Assert.assertEquals(apiEx.getResponseCode(), 404); } } diff --git a/src/test/java/com/box/sdk/BoxFolderTest.java b/src/test/java/com/box/sdk/BoxFolderTest.java index 5da3f0e12..1a94b1a42 100644 --- a/src/test/java/com/box/sdk/BoxFolderTest.java +++ b/src/test/java/com/box/sdk/BoxFolderTest.java @@ -1093,10 +1093,10 @@ public void uploadSessionAbortFlowSuccess() throws Exception { Assert.assertNotNull(endpoints.getAbortEndpoint()); //Verify the status of the session - getUploadSessionStatus(session.getResource()); + this.getUploadSessionStatus(session.getResource()); //Verify the delete session - abortUploadSession(session.getResource()); + this.abortUploadSession(session.getResource()); } finally { uploadedFile.delete(); } @@ -1118,7 +1118,7 @@ private void abortUploadSession(BoxFileUploadSession session) { //If the session is aborted, this line should not be executed. Assert.assertFalse("Upload session is not deleted", true); - } catch(BoxAPIException apiEx) { + } catch (BoxAPIException apiEx) { Assert.assertEquals(apiEx.getResponseCode(), 404); } } From faf746b6f17b015dccf254fe478ba2a5738a2cb5 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Thu, 2 Mar 2017 17:29:22 -0800 Subject: [PATCH 072/119] BoxFile.Info variable added in files.md --- doc/files.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/files.md b/doc/files.md index 6b7113c74..67d997f7e 100644 --- a/doc/files.md +++ b/doc/files.md @@ -111,7 +111,7 @@ Files are uploaded to a folder by calling the ```java BoxFolder rootFolder = BoxFolder.getRootFolder(api); FileInputStream stream = new FileInputStream("My File.txt"); -rootFolder.uploadFile(stream, "My File.txt"); +BoxFile.Info newFileInfo = rootFolder.uploadFile(stream, "My File.txt"); stream.close(); ``` @@ -123,7 +123,7 @@ Upload progress can be tracked by providing the size of the file and a ```java BoxFolder rootFolder = BoxFolder.getRootFolder(api); FileInputStream stream = new FileInputStream("My File.txt"); -rootFolder.uploadFile(stream, "My File.txt", 1024, new ProgressListener() { +BoxFile.Info newFileInfo = rootFolder.uploadFile(stream, "My File.txt", 1024, new ProgressListener() { public void onProgressChanged(long numBytes, long totalBytes) { double percentComplete = numBytes / totalBytes; } @@ -371,4 +371,4 @@ for (Metadata metadata : metadataList) { } ``` -[get-all-metadata]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFile.html#getAllMetadata(java.lang.String...) \ No newline at end of file +[get-all-metadata]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFile.html#getAllMetadata(java.lang.String...) From 8921da7ad2179f70319916292074bccf19d95084 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Sun, 5 Mar 2017 21:53:08 -0800 Subject: [PATCH 073/119] getAuthorizationURL implementation --- .../java/com/box/sdk/BoxAPIConnection.java | 36 +++++++++++++++++++ .../com/box/sdk/BoxAPIConnectionTest.java | 35 ++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/src/main/java/com/box/sdk/BoxAPIConnection.java b/src/main/java/com/box/sdk/BoxAPIConnection.java index 896e0d7c3..32065e454 100644 --- a/src/main/java/com/box/sdk/BoxAPIConnection.java +++ b/src/main/java/com/box/sdk/BoxAPIConnection.java @@ -2,6 +2,7 @@ import java.net.MalformedURLException; import java.net.Proxy; +import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.List; @@ -23,6 +24,7 @@ public class BoxAPIConnection { */ public static final int DEFAULT_MAX_ATTEMPTS = 3; + private static final String AUTHORIZATION_URL = "https://account.box.com/api/oauth2/authorize"; private static final String TOKEN_URL_STRING = "https://api.box.com/oauth2/token"; private static final String DEFAULT_BASE_URL = "https://api.box.com/2.0/"; private static final String DEFAULT_BASE_UPLOAD_URL = "https://upload.box.com/api/2.0/"; @@ -125,6 +127,40 @@ public static BoxAPIConnection restore(String clientID, String clientSecret, Str return api; } + /** + * Return the authorization URL which is used to perform the authorization_code based OAuth2 flow. + * @param clientID the client ID to use with the connection. + * @param redirectUri the URL to which Box redirects the browser when authentication completes. + * @param state the text string that you choose. + * Box sends the same string to your redirect URL when authentication is complete. + * @param scopes this optional parameter identifies the Box scopes available + * to the application once it's authenticated. + * @return the authorization URL + */ + public static URL getAuthorizationURL(String clientID, URI redirectUri, String state, List scopes) { + URLTemplate template = new URLTemplate(AUTHORIZATION_URL); + QueryStringBuilder queryBuilder = new QueryStringBuilder().appendParam("client_id", clientID) + .appendParam("response_type", "code") + .appendParam("redirect_uri", redirectUri.toString()) + .appendParam("state", state); + + if (scopes != null && !scopes.isEmpty()) { + StringBuilder builder = new StringBuilder(); + int size = scopes.size() - 1; + int i = 0; + while (i < size) { + builder.append(scopes.get(i)); + builder.append(","); + i++; + } + builder.append(scopes.get(i)); + + queryBuilder.appendParam("scope", builder.toString()); + } + + return template.buildWithQuery("", queryBuilder.toString()); + } + /** * Authenticates the API connection by obtaining access and refresh tokens using the auth code that was obtained * from the first half of OAuth. diff --git a/src/test/java/com/box/sdk/BoxAPIConnectionTest.java b/src/test/java/com/box/sdk/BoxAPIConnectionTest.java index c37cd6ce9..a4ea5bb18 100644 --- a/src/test/java/com/box/sdk/BoxAPIConnectionTest.java +++ b/src/test/java/com/box/sdk/BoxAPIConnectionTest.java @@ -1,7 +1,11 @@ package com.box.sdk; import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertFalse; @@ -11,6 +15,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -119,6 +124,36 @@ public String getJSON() { assertFalse(restoredAPI.needsRefresh()); } + @Test + @Category(UnitTest.class) + public void getAuthorizetionURLSuccess() throws Exception { + List scopes = new ArrayList(); + scopes.add("root_readwrite"); + + URL authURL = BoxAPIConnection.getAuthorizationURL("wncmz88sacf5oyaxf502dybcruqbzzy0", + new URI("http://localhost:3000"), "test", scopes); + + System.out.println("Response: " + authURL.toString()); + + Assert.assertTrue(authURL.toString().startsWith("https://account.box.com/api/oauth2/authorize")); + + StringTokenizer tokenizer = new StringTokenizer(authURL.getQuery(), "&"); + while (tokenizer.hasMoreTokens()) { + String token = tokenizer.nextToken(); + if (token.startsWith("client_id")) { + Assert.assertEquals(token, "client_id=wncmz88sacf5oyaxf502dybcruqbzzy0"); + } else if (token.startsWith("response_type")) { + Assert.assertEquals(token, "response_type=code"); + } else if (token.startsWith("redirect_uri")) { + Assert.assertEquals(token, "redirect_uri=http%3A%2F%2Flocalhost%3A3000"); + } else if (token.startsWith("state")) { + Assert.assertEquals(token, "state=test"); + } else if (token.startsWith("scope")) { + Assert.assertEquals(token, "scope=root_readwrite"); + } + } + } + @Test @Category(IntegrationTest.class) public void requestIsSentNormallyWhenInterceptorReturnsNullResponse() throws MalformedURLException { From 1583029bdef02124e2859cab282760a1ca6dc95f Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Mon, 6 Mar 2017 13:48:57 -0800 Subject: [PATCH 074/119] Scopes are delimited by spaces --- src/main/java/com/box/sdk/BoxAPIConnection.java | 2 +- src/test/java/com/box/sdk/BoxAPIConnectionTest.java | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxAPIConnection.java b/src/main/java/com/box/sdk/BoxAPIConnection.java index 32065e454..709a58726 100644 --- a/src/main/java/com/box/sdk/BoxAPIConnection.java +++ b/src/main/java/com/box/sdk/BoxAPIConnection.java @@ -150,7 +150,7 @@ public static URL getAuthorizationURL(String clientID, URI redirectUri, String s int i = 0; while (i < size) { builder.append(scopes.get(i)); - builder.append(","); + builder.append(" "); i++; } builder.append(scopes.get(i)); diff --git a/src/test/java/com/box/sdk/BoxAPIConnectionTest.java b/src/test/java/com/box/sdk/BoxAPIConnectionTest.java index a4ea5bb18..895ac561a 100644 --- a/src/test/java/com/box/sdk/BoxAPIConnectionTest.java +++ b/src/test/java/com/box/sdk/BoxAPIConnectionTest.java @@ -129,12 +129,11 @@ public String getJSON() { public void getAuthorizetionURLSuccess() throws Exception { List scopes = new ArrayList(); scopes.add("root_readwrite"); + scopes.add("manage_groups"); URL authURL = BoxAPIConnection.getAuthorizationURL("wncmz88sacf5oyaxf502dybcruqbzzy0", new URI("http://localhost:3000"), "test", scopes); - System.out.println("Response: " + authURL.toString()); - Assert.assertTrue(authURL.toString().startsWith("https://account.box.com/api/oauth2/authorize")); StringTokenizer tokenizer = new StringTokenizer(authURL.getQuery(), "&"); @@ -149,7 +148,7 @@ public void getAuthorizetionURLSuccess() throws Exception { } else if (token.startsWith("state")) { Assert.assertEquals(token, "state=test"); } else if (token.startsWith("scope")) { - Assert.assertEquals(token, "scope=root_readwrite"); + Assert.assertEquals(token, "scope=root_readwrite+manage_groups"); } } } From 7a75bcf2d5ba332c5d0d5329336b0903af246549 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Tue, 7 Mar 2017 11:29:33 -0800 Subject: [PATCH 075/119] Files examples updates --- doc/files.md | 229 ++++++++++++++++++ .../com/box/sdk/BoxFileUploadSession.java | 4 +- .../java/com/box/sdk/LargeFileUpload.java | 5 +- src/test/java/com/box/sdk/BoxFileTest.java | 6 +- src/test/java/com/box/sdk/BoxFolderTest.java | 6 +- 5 files changed, 239 insertions(+), 11 deletions(-) diff --git a/doc/files.md b/doc/files.md index 67d997f7e..44fcb8908 100644 --- a/doc/files.md +++ b/doc/files.md @@ -134,6 +134,119 @@ stream.close(); [upload]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFolder.html#uploadFile(java.io.InputStream,%20java.lang.String) [upload2]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFolder.html#uploadFile(java.io.InputStream,%20java.lang.String,%20long,%20com.box.sdk.ProgressListener) +Upload a large File in chunks +-------------------------------------- + +An upload session can be created with the [`createUploadSession(fileName, fileSize)`][create-upload-session] method to +upload a large file in chunks. + +```java +//Create the upload session +BoxFile file = new BoxFile(api, "id"); +BoxFileUploadSession.Info sessionInfo = file.createUploadSession("My_Large_File.txt", fileSize); + +//Get the session resource from the session info +BoxFileUploadSession session = sessionInfo.getResource(); + +//Create the Message Digest for the whole file +MessageDigest digest = null; +try { + digest = MessageDigest.getInstance("SHA1"); +} catch (NoSuchAlgorithmException ae) { + throw new BoxAPIException("Digest algorithm not found", ae); +} +``` +Once the upload session is created, using that session the large file can be uploaded in chuncks with the +[`uploadPart(partId, stream, offset, partSize, totalSizeOfFile)`][upload-part] method of the session instance. +If there is a failure in uploading any of the parts, +the failed part can be uploaded again without affecting the other parts. + +```java +//Reading a large file +FileInputStream fis = new FileInputStream("My_Large_File.txt"); +//Create the digest input stream to calculate the digest for the whole file. +DigestInputStream dis = new DigestInputStream(fis, digest); + +List parts = new ArrayList(); + +//Get the part size. Each uploaded part should match the part size returned as part of the upload session. +//The last part of the file can be less than part size if the remaining bytes of the last part is less than +//the given part size +long partSize = sessionInfo.getPartSize(); +//Start byte of the part +long offset = 0; +//Overall of bytes processed so far +long processed = 0; +while (processed < fileSize) { + long diff = fileSize - processed; + //The size last part of the file can be lesser than the part size. + if (diff < partSize) { + partSize = diff; + } + + //Generate a unique partId + String partId = LargeFileUpload.generateHex(); + //Upload a part. It can be uploaded asynchorously + BoxFileUploadSessionPart part = session.uploadPart(partId, dis, offset, partSize, fileSize); + parts.add(part); + + //Increase the offset and proceesed bytes to calculate the Content-Range header. + processed += partSize; + offset += partSize; +} +``` + +At any point in time, the list of parts that are being uploaded successfully can be retrivied with the +[`listParts(marker, limit)`][list-parts] method of the session instance. + +```java +//The following snippet retrives first 1000 parts that are uploaded. Both can be modified based on the needs. +BoxFileUploadSessionPartList partList = session.listParts(0, 1000); +List parts = partList.getParts(); +``` +Once all the parts are uploaded successfully. the upload sessiion can be commited with the +[`commit(digest, parts, attributes, ifMatch, ifNoneMatch)`][upload-session-commit] method. + +```java +//Creates the file hash +byte[] digestBytes = digest.digest(); +//Base64 encoding of the hash +String digestStr = Base64.encode(digestBytes); + +//Commit the upload session. If there is a failure, abort the commit. +BoxFile.Info fileInfo = session.commit(digestStr, parts, null, null, null); +``` + +The upload session can be aborted at any time with the [`abort()`][upload-session-abort] method of the session instance. + +```java +session.abort(); +``` + +The upload session status can be retrived at any time with the [`getstatus()`][upload-session-status] method. +This call will update the parts processed and other information in the session info instance. +```java +BoxFileUploadSession.Info updatedSessionInfo = session.getStatus(); +``` + +[create-upload-session]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFolder.html#createUploadSession(java.lang.String,%20long) + +Create a large File +------------------- + +A large file can be uploaded with the [`uploadLargeFile(InputStream, fileName, fileSize)`][upload-large-file] method. + +```java +File myFile = new File("My Large_File.txt"); +FileInputStream stream = new FileInputStream(myFile); + +BoxFolder rootFolder = BoxFolder.getRootFolder(api); +BoxFile.Info fileInfo = rootFolder.uploadLargeFile(inputStream, "My_Large_File.txt", myFile.length()); +``` + +[upload-large-file]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFolder.html#uploadLargeFile(java.io.InputStream,%20java.lang.String,%20long) + + Copy a File ----------- @@ -239,6 +352,122 @@ firstVersion.delete(); [delete-version]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFileVersion.html#delete() +Create a versioning of a large File by uploading its content in chunks +---------------------------------------------------------------------- + +An upload session can be created with the [`createUploadSession(fileSize)`][create-upload-session-version] +method to upload new version of a large file in chunks. + +```java +BoxFile file = new BoxFile(api, "id"); +BoxFileUploadSession.Info session = file.createUploadSession(fileSize); + +//Get the session resource from the session info +BoxFileUploadSession session = sessionInfo.getResource(); + +//Create the Message Digest for the whole file +MessageDigest digest = null; +try { + digest = MessageDigest.getInstance("SHA1"); +} catch (NoSuchAlgorithmException ae) { + throw new BoxAPIException("Digest algorithm not found", ae); +} +``` +Once the upload session is created, the large file can be uploaded in chuncks with the +[`uploadPart(partId, stream, offset, partSize, totalSizeOfFile)`][upload-part] method of the session instance. +If there is a failure in uploading any of the parts, the failed part can be uploaded again without +affecting the other parts. + +```java +//Reading a large file +FileInputStream fis = new FileInputStream("My_Large_File.txt"); +//Create the digest input stream to calculate the digest for the whole file. +DigestInputStream dis = new DigestInputStream(fis, digest); + +List parts = new ArrayList(); + +//Get the part size. Each uploaded part should match the part size returned as part of the upload session. +//The last part of the file can be less than part size if the remaining bytes of the last part is less than +//the given part size +long partSize = sessionInfo.getPartSize(); +//Start byte of the part +long offset = 0; +//Overall of bytes processed so far +long processed = 0; +while (processed < fileSize) { + long diff = fileSize - processed; + //The size last part of the file can be lesser than the part size. + if (diff < partSize) { + partSize = diff; + } + + //Generate a unique partId + String partId = LargeFileUpload.generateHex(); + //Upload a part. It can be uploaded asynchorously + BoxFileUploadSessionPart part = session.uploadPart(partId, dis, offset, partSize, fileSize); + parts.add(part); + + //Increase the offset and proceesed bytes to calculate the Content-Range header. + processed += partSize; + offset += partSize; +} +``` +At any point in time, the list of parts that are being uploaded successfully can be retrivied with the +[`listParts(marker, limit)`][list-parts] method of the session instance. + +```java +//The following snippet retrives first 1000 parts that are uploaded. Both can be modified based on the needs. +BoxFileUploadSessionPartList partList = session.listParts(0, 1000); +List parts = partList.getParts(); +``` +Once all the parts are uploaded successfully. the upload sessiion can be commited with the +[`commit(digest, parts, attributes, ifMatch, ifNoneMatch)`][upload-session-commit] method. + +```java +//Creates the file hash +byte[] digestBytes = digest.digest(); +//Base64 encoding of the hash +String digestStr = Base64.encode(digestBytes); + +//Commit the upload session. If there is a failure, abort the commit. +BoxFile.Info fileInfo = session.commit(digestStr, parts, null, null, null); +``` + +The upload session can be aborted at any time with the [`abort()`][upload-session-abort] method of the session instance. + +```java +session.abort(); +``` + +The upload session status can be retrived at any time with the [`getstatus()`][upload-session-status] method. +This call will update the parts processed and other information in the session info instance. +```java +BoxFileUploadSession.Info sessionInfo = session.getStatus(); +``` + +[create-upload-session-version]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFile.html#uploadVersion(long) +[upload-part]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFileUploadSession.html#uploadPart(java.lang.String,%20java.io.InputStream,%20long,%20long,%20long) +[list-parts]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFileUploadSession.html#listParts(int,%20int) +[upload-session-commit]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFileUploadSession.html#commit(java.lang.String,%20java.util.List,%20java.util.Map,%20java.lang.String,%20java.lang.String) +[upload-session-abort]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFileUploadSession.html#abort() +[upload-session-status]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFileUploadSession.html#getStatus() + +Create new version of a large File +---------------------------------- + +New versions of a large file can be uploaded with the +[`uploadLargeFile(InputStream, fileSize)`][upload-large-file-version] method. + +```java +File myFile = new File("My File.txt"); +FileInputStream stream = new FileInputStream(myFile); + +BoxFile file = new BoxFile(api, "id"); +BoxFile.Info versionedFileInfo = file.uploadLargeFile(inputStream, myFile.length()); +``` + +[upload-large-file-version]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFile.html#uploadLargeFile(java.io.InputStream,%20long) + Lock a File ----------- diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index 995c5268f..11bb2f621 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -407,7 +407,7 @@ private String getCommitBody(List parts, Map Date: Tue, 7 Mar 2017 13:58:03 -0800 Subject: [PATCH 076/119] large file upload test file input modified --- src/test/java/com/box/sdk/BoxFileTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index a2683a309..71835d737 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -1214,7 +1214,10 @@ private static byte[] readAllBytes(String fileName) throws IOException { @Test @Category(IntegrationTest.class) public void uploadLargeFile() throws Exception { - File file = new File("/Users/kshanmugasundaram/Downloads/tenmb"); + String fileName = "Tamme-Lauri_tamm_suvepäeval.jpg"; + URL fileURL = this.getClass().getResource("/sample-files/" + fileName); + String filePath = URLDecoder.decode(fileURL.getFile(), "utf-8"); + File file = new File(filePath); FileInputStream stream = new FileInputStream(file); BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); From 6a3ed5680532ddce096e08202ed721a7d938e220 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Tue, 7 Mar 2017 14:13:34 -0800 Subject: [PATCH 077/119] 1st cut at single file collab. adds to Box file --- build.gradle | 11 ++++ src/main/java/com/box/sdk/BoxFile.java | 64 +++++++++++++++++++ .../com/box/sdk/BoxCollaborationTest.java | 34 ++++++++++ .../com/box/sdk/IntegrationTestDebug.java | 7 ++ 4 files changed, 116 insertions(+) create mode 100644 src/test/java/com/box/sdk/IntegrationTestDebug.java diff --git a/build.gradle b/build.gradle index 76c9fde6d..6fd312a8c 100644 --- a/build.gradle +++ b/build.gradle @@ -95,6 +95,16 @@ task integrationTest(type: Test) { } } +task integrationTestDebug(type: Test) { + description 'Runs the integration tests.' + group 'Verification' + testLogging.showStandardStreams = true + useJUnit { + includeCategories 'com.box.sdk.IntegrationTestDebug' + + } +} + task integrationTestJWT(type: Test) { description 'Runs the JWT based integration tests.' group 'Verification' @@ -136,6 +146,7 @@ artifacts { test { useJUnit { excludeCategories 'com.box.sdk.IntegrationTest' + excludeCategories 'com.box.sdk.IntegrationTestDebug' excludeCategories 'com.box.sdk.IntegrationTestJWT' } } diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 730b38ad9..023489442 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -63,6 +63,9 @@ public enum ThumbnailFileType { private static final URLTemplate GET_TASKS_URL_TEMPLATE = new URLTemplate("files/%s/tasks"); private static final URLTemplate GET_THUMBNAIL_PNG_TEMPLATE = new URLTemplate("files/%s/thumbnail.png"); private static final URLTemplate GET_THUMBNAIL_JPG_TEMPLATE = new URLTemplate("files/%s/thumbnail.jpg"); + private static final URLTemplate ADD_COLLABORATION_URL = new URLTemplate("collaborations"); + private static final URLTemplate GET_COLLABORATIONS_URL = new URLTemplate("folders/%s/collaborations"); + private static final int BUFFER_SIZE = 8192; @@ -1112,4 +1115,65 @@ String toJSONValue() { return this.jsonValue; } } + private BoxCollaboration.Info collaborate(JsonObject accessibleByField, BoxCollaboration.Role role) { + BoxAPIConnection api = this.getAPI(); + URL url = ADD_COLLABORATION_URL.build(api.getBaseURL()); + + JsonObject itemField = new JsonObject(); + itemField.add("id", this.getID()); + itemField.add("type", "file"); + + JsonObject requestJSON = new JsonObject(); + requestJSON.add("item", itemField); + requestJSON.add("accessible_by", accessibleByField); + requestJSON.add("role", role.toJSONString()); + + BoxJSONRequest request = new BoxJSONRequest(api, url, "POST"); + request.setBody(requestJSON.toString()); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); + + BoxCollaboration newCollaboration = new BoxCollaboration(api, responseJSON.get("id").asString()); + BoxCollaboration.Info info = newCollaboration.new Info(responseJSON); + return info; + } + /** + * Adds a collaborator to this file. + * @param collaborator the collaborator to add. + * @param role the role of the collaborator. + * @return info about the new collaboration. + */ + public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role) { + JsonObject accessibleByField = new JsonObject(); + accessibleByField.add("id", collaborator.getID()); + + if (collaborator instanceof BoxUser) { + accessibleByField.add("type", "user"); + } else if (collaborator instanceof BoxGroup) { + accessibleByField.add("type", "group"); + } else { + throw new IllegalArgumentException("The given collaborator is of an unknown type."); + } + + return this.collaborate(accessibleByField, role); + } + + + /** + * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't already have a Box + * account. + * @param email the email address of the collaborator to add. + * @param role the role of the collaborator. + * @return info about the new collaboration. + */ + public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role) { + JsonObject accessibleByField = new JsonObject(); + accessibleByField.add("login", email); + accessibleByField.add("type", "user"); + + return this.collaborate(accessibleByField, role); + } + + + } diff --git a/src/test/java/com/box/sdk/BoxCollaborationTest.java b/src/test/java/com/box/sdk/BoxCollaborationTest.java index 6be3d8240..fd325d4ad 100644 --- a/src/test/java/com/box/sdk/BoxCollaborationTest.java +++ b/src/test/java/com/box/sdk/BoxCollaborationTest.java @@ -7,6 +7,10 @@ import org.junit.Test; import org.junit.experimental.categories.Category; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.logging.Logger; + public class BoxCollaborationTest { @Test @Category(IntegrationTest.class) @@ -50,4 +54,34 @@ public void deleteSucceeds() { folder.delete(false); } + + @Test + @Category(IntegrationTestDebug.class) + public void singleFileCollabSucceeds() { + Logger logger = TestConfig.enableLogger("FINE"); + BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); + BoxFolder rootFolder = BoxFolder.getRootFolder(api); + String fileName = "[singleFileCollabSucceeds] Test File.txt"; + String fileContent = "Test file"; + byte[] fileBytes = fileContent.getBytes(StandardCharsets.UTF_8); + InputStream uploadStream = new ByteArrayInputStream(fileBytes); + BoxFile uploadedFile = rootFolder.uploadFile(uploadStream, fileName).getResource(); + + String collaboratorLogin = TestConfig.getCollaborator(); + BoxCollaboration.Role originalRole = BoxCollaboration.Role.VIEWER; + BoxCollaboration.Role newRole = BoxCollaboration.Role.EDITOR; + + BoxCollaboration.Info collabInfo = uploadedFile.collaborate(collaboratorLogin, originalRole); + + assertThat(collabInfo.getRole(), is(equalTo(originalRole))); + + BoxCollaboration collab = collabInfo.getResource(); + collabInfo.setRole(newRole); + collab.updateInfo(collabInfo); + + assertThat(collabInfo.getRole(), is(equalTo(newRole))); + + uploadedFile.delete(); + } + } diff --git a/src/test/java/com/box/sdk/IntegrationTestDebug.java b/src/test/java/com/box/sdk/IntegrationTestDebug.java new file mode 100644 index 000000000..233180a55 --- /dev/null +++ b/src/test/java/com/box/sdk/IntegrationTestDebug.java @@ -0,0 +1,7 @@ +package com.box.sdk; + +/** + * Created by dmaynard on 3/7/17. + */ +public interface IntegrationTestDebug { +} From 91b0b62b74d468544073abcff381c8a86ab287b5 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Tue, 7 Mar 2017 16:17:56 -0800 Subject: [PATCH 078/119] list parts marker fix --- .../java/com/box/sdk/BoxFileUploadSession.java | 12 +++++++----- src/test/java/com/box/sdk/BoxFileTest.java | 18 ++++++++++++------ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index 11bb2f621..08f61edce 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -289,14 +289,16 @@ public BoxFileUploadSessionPart uploadPart(String partId, InputStream stream, lo * @param limit maximum number of parts to return. * @return the list of parts. */ - public BoxFileUploadSessionPartList listParts(int marker, int limit) { + public BoxFileUploadSessionPartList listParts(String marker, int limit) { URL listPartsURL = this.sessionInfo.getSessionEndpoints().getListPartsEndpoint(); URLTemplate template = new URLTemplate(listPartsURL.toString()); - String queryString = new QueryStringBuilder() - .appendParam(MARKER_QUERY_STRING, marker) - .appendParam(LIMIT_QUERY_STRING, limit) - .toString(); + QueryStringBuilder builder = new QueryStringBuilder(); + if (marker != null) { + builder.appendParam(MARKER_QUERY_STRING, marker); + } + String queryString = builder.appendParam(LIMIT_QUERY_STRING, limit).toString(); + //Template is initalized with the full URL. So empty string for the path. URL url = template.buildWithQuery("", queryString); diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index 71835d737..29b96cd77 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -1134,14 +1134,20 @@ private BoxFile.Info createImageFile(BoxFolder folder) throws IOException { @Test @Category(IntegrationTest.class) public void uploadSessionAbortFlowSuccess() throws Exception { - BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); - BoxFolder rootFolder = BoxFolder.getRootFolder(api); + String fileName = "Tamme-Lauri_tamm_suvepäeval.jpg"; + URL fileURL = this.getClass().getResource("/sample-files/" + fileName); + String filePath = URLDecoder.decode(fileURL.getFile(), "utf-8"); + File file = new File(filePath); + long fileSize = file.length(); - String fileName = "[setCollectionsWithInfoSucceeds] Test File.txt"; - String fileContent = "Test file"; - byte[] fileBytes = fileContent.getBytes(StandardCharsets.UTF_8); + FileInputStream stream = new FileInputStream(file); + byte[] fileBytes = new byte[(int) file.length()]; + stream.read(fileBytes); InputStream uploadStream = new ByteArrayInputStream(fileBytes); + + BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); + BoxFolder rootFolder = BoxFolder.getRootFolder(api); BoxFile uploadedFile = rootFolder.uploadFile(uploadStream, fileName).getResource(); try { BoxFileUploadSession.Info session = uploadedFile.createUploadSession(fileBytes.length); @@ -1168,7 +1174,7 @@ public void uploadSessionAbortFlowSuccess() throws Exception { } private List listUploadSessionParts(BoxFileUploadSession session) { - BoxFileUploadSessionPartList list = session.listParts(0, 10); + BoxFileUploadSessionPartList list = session.listParts(null, 10); List parts = list.getParts(); Assert.assertEquals(parts.size(), 3); From fbd651866e31673d4932c07a94cfe4f90b019bda Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Tue, 7 Mar 2017 16:23:58 -0800 Subject: [PATCH 079/119] Abort flow implementation --- src/test/java/com/box/sdk/BoxFolderTest.java | 58 +++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/src/test/java/com/box/sdk/BoxFolderTest.java b/src/test/java/com/box/sdk/BoxFolderTest.java index c8a8e78d9..1df2ef634 100644 --- a/src/test/java/com/box/sdk/BoxFolderTest.java +++ b/src/test/java/com/box/sdk/BoxFolderTest.java @@ -1,9 +1,12 @@ package com.box.sdk; import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; +import java.net.URLDecoder; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Collection; @@ -1072,34 +1075,37 @@ public void uploadSessionAbortFlowSuccess() throws Exception { BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); BoxFolder rootFolder = BoxFolder.getRootFolder(api); - String fileName = "[setCollectionsWithInfoSucceeds] Test File.txt"; - String fileContent = "Test file"; - byte[] fileBytes = fileContent.getBytes(StandardCharsets.UTF_8); + String fileName = "Tamme-Lauri_tamm_suvepäeval.jpg"; + URL fileURL = this.getClass().getResource("/sample-files/" + fileName); + String filePath = URLDecoder.decode(fileURL.getFile(), "utf-8"); + File file = new File(filePath); + long fileSize = file.length(); + FileInputStream stream = new FileInputStream(file); + + byte[] fileBytes = new byte[(int) file.length()]; + stream.read(fileBytes); InputStream uploadStream = new ByteArrayInputStream(fileBytes); - BoxFile uploadedFile = rootFolder.uploadFile(uploadStream, fileName).getResource(); - try { - BoxFileUploadSession.Info session = uploadedFile.createUploadSession(fileBytes.length); - Assert.assertNotNull(session.getUploadSessionId()); - Assert.assertNotNull(session.getSessionExpiresAt()); - Assert.assertNotNull(session.getPartSize()); - - BoxFileUploadSession.Endpoints endpoints = session.getSessionEndpoints(); - Assert.assertNotNull(endpoints); - Assert.assertNotNull(endpoints.getUploadPartEndpoint()); - Assert.assertNotNull(endpoints.getStatusEndpoint()); - Assert.assertNotNull(endpoints.getListPartsEndpoint()); - Assert.assertNotNull(endpoints.getCommitEndpoint()); - Assert.assertNotNull(endpoints.getAbortEndpoint()); - - //Verify the status of the session - this.getUploadSessionStatus(session.getResource()); - - //Verify the delete session - this.abortUploadSession(session.getResource()); - } finally { - uploadedFile.delete(); - } + + BoxFileUploadSession.Info session = rootFolder.createUploadSession( + "Tamme-Lauri_tamm_suvepäeval.jpg", fileBytes.length); + Assert.assertNotNull(session.getUploadSessionId()); + Assert.assertNotNull(session.getSessionExpiresAt()); + Assert.assertNotNull(session.getPartSize()); + + BoxFileUploadSession.Endpoints endpoints = session.getSessionEndpoints(); + Assert.assertNotNull(endpoints); + Assert.assertNotNull(endpoints.getUploadPartEndpoint()); + Assert.assertNotNull(endpoints.getStatusEndpoint()); + Assert.assertNotNull(endpoints.getListPartsEndpoint()); + Assert.assertNotNull(endpoints.getCommitEndpoint()); + Assert.assertNotNull(endpoints.getAbortEndpoint()); + + //Verify the status of the session + this.getUploadSessionStatus(session.getResource()); + + //Verify the delete session + this.abortUploadSession(session.getResource()); } private void getUploadSessionStatus(BoxFileUploadSession session) { From 506de44f319202fb5457b60e104003c1414f0d62 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Thu, 9 Mar 2017 09:16:45 -0800 Subject: [PATCH 080/119] Encoding utf-8 is enabled for the build --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 76c9fde6d..d5e199739 100644 --- a/build.gradle +++ b/build.gradle @@ -42,6 +42,7 @@ javadoc { options.noHelp true options.noDeprecatedList true options.noNavBar true + options.encoding 'utf-8' options.docEncoding 'utf-8' options.charSet 'utf-8' options.linkSource true From ce816e99c4a6223c47940f782998a614956946e0 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Tue, 14 Mar 2017 16:21:03 -0700 Subject: [PATCH 081/119] Adding Single file Collabs and Integration tests fixes for FolderCollabs --- .../java/com/box/sdk/BoxCollaboration.java | 75 ++++++-- src/main/java/com/box/sdk/BoxFile.java | 176 ++++++++++++------ src/main/java/com/box/sdk/BoxFolder.java | 78 ++++++++ .../java/com/box/sdk/BoxResourceIterable.java | 2 +- .../com/box/sdk/BoxCollaborationTest.java | 74 +++++++- src/test/java/com/box/sdk/BoxFolderTest.java | 76 ++++---- 6 files changed, 371 insertions(+), 110 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxCollaboration.java b/src/main/java/com/box/sdk/BoxCollaboration.java index 255405d44..7c1080eb6 100644 --- a/src/main/java/com/box/sdk/BoxCollaboration.java +++ b/src/main/java/com/box/sdk/BoxCollaboration.java @@ -23,20 +23,24 @@ public class BoxCollaboration extends BoxResource { private static final URLTemplate COLLABORATIONS_URL_TEMPLATE = new URLTemplate("collaborations"); private static final URLTemplate PENDING_COLLABORATIONS_URL = new URLTemplate("collaborations?status=pending"); private static final URLTemplate COLLABORATION_URL_TEMPLATE = new URLTemplate("collaborations/%s"); + private static final URLTemplate GET_ALL_FILE_COLLABORATIONS_URL = new URLTemplate("files/%s/collaborations/"); /** * Constructs a BoxCollaboration for a collaboration with a given ID. - * @param api the API connection to be used by the collaboration. - * @param id the ID of the collaboration. + * + * @param api the API connection to be used by the collaboration. + * @param id the ID of the collaboration. */ public BoxCollaboration(BoxAPIConnection api, String id) { super(api, id); } + /** * Gets all pending collaboration invites for the current user. - * @param api the API connection to use. - * @return a collection of pending collaboration infos. + * + * @param api the API connection to use. + * @return a collection of pending collaboration infos. */ public static Collection getPendingCollaborations(BoxAPIConnection api) { URL url = PENDING_COLLABORATIONS_URL.build(api.getBaseURL()); @@ -60,6 +64,7 @@ public static Collection getPendingCollaborations(BoxAPIConnection api) { /** * Gets information about this collaboration. + * * @return info about this collaboration. */ public Info getInfo() { @@ -74,6 +79,7 @@ public Info getInfo() { /** * Updates the information about this collaboration with any info fields that have been modified locally. + * * @param info the updated info. */ public void updateInfo(Info info) { @@ -116,6 +122,7 @@ public class Info extends BoxResource.Info { private Role role; private Date acknowledgedAt; private BoxFolder.Info item; + private BoxFile.Info fileItem; /** * Constructs an empty Info object. @@ -126,7 +133,8 @@ public Info() { /** * Constructs an Info object by parsing information from a JSON string. - * @param json the JSON string to parse. + * + * @param json the JSON string to parse. */ public Info(String json) { super(json); @@ -138,6 +146,7 @@ public Info(String json) { /** * Gets the user who created the collaboration. + * * @return the user who created the collaboration. */ public BoxUser.Info getCreatedBy() { @@ -146,6 +155,7 @@ public BoxUser.Info getCreatedBy() { /** * Gets the time the collaboration was created. + * * @return the time the collaboration was created. */ public Date getCreatedAt() { @@ -154,6 +164,7 @@ public Date getCreatedAt() { /** * Gets the time the collaboration was last modified. + * * @return the time the collaboration was last modified. */ public Date getModifiedAt() { @@ -162,6 +173,7 @@ public Date getModifiedAt() { /** * Gets the time the collaboration will expire. + * * @return the time the collaboration will expire. */ public Date getExpiresAt() { @@ -170,6 +182,7 @@ public Date getExpiresAt() { /** * Gets the status of the collaboration. + * * @return the status of the collaboration. */ public Status getStatus() { @@ -178,6 +191,7 @@ public Status getStatus() { /** * Sets the status of the collaboration in order to accept or reject the collaboration if it's pending. + * * @param status the new status of the collaboration. */ public void setStatus(Status status) { @@ -187,6 +201,7 @@ public void setStatus(Status status) { /** * Gets the collaborator who this collaboration applies to. + * * @return the collaborator who this collaboration applies to. */ public BoxCollaborator.Info getAccessibleBy() { @@ -195,6 +210,7 @@ public BoxCollaborator.Info getAccessibleBy() { /** * Gets the level of access the collaborator has. + * * @return the level of access the collaborator has. */ public Role getRole() { @@ -203,6 +219,7 @@ public Role getRole() { /** * Sets the level of access the collaborator has. + * * @param role the new level of access to give the collaborator. */ public void setRole(Role role) { @@ -212,6 +229,7 @@ public void setRole(Role role) { /** * Gets the time the collaboration's status was changed. + * * @return the time the collaboration's status was changed. */ public Date getAcknowledgedAt() { @@ -220,6 +238,7 @@ public Date getAcknowledgedAt() { /** * Gets the folder the collaboration is related to. + * * @return the folder the collaboration is related to. */ public BoxFolder.Info getItem() { @@ -292,7 +311,7 @@ protected void parseJSONMember(JsonObject.Member member) { private void updateAccessibleBy(JsonObject json) { String type = json.get("type").asString(); if ((type.equals("user") && this.accessibleBy instanceof BoxUser.Info) - || (type.equals("group") && this.accessibleBy instanceof BoxGroup.Info)) { + || (type.equals("group") && this.accessibleBy instanceof BoxGroup.Info)) { this.accessibleBy.update(json); } else { @@ -345,28 +364,28 @@ public enum Role { * download, upload, edit, delete, copy, move, rename, generate shared links, make comments, assign tasks, * create tags, and invite/remove collaborators. They will not be able to delete or move root level folders. */ - EDITOR ("editor"), + EDITOR("editor"), /** * The viewer role has full read access to a folder. Once invited to a folder, they will be able to preview, * download, make comments, and generate shared links. They will not be able to add tags, invite new * collaborators, upload, edit, or delete items in the folder. */ - VIEWER ("viewer"), + VIEWER("viewer"), /** * The previewer role has limited read access to a folder. They will only be able to preview the items in the * folder using the integrated content viewer. They will not be able to share, upload, edit, or delete any * content. This role is only available to enterprise accounts. */ - PREVIEWER ("previewer"), + PREVIEWER("previewer"), /** * The uploader has limited write access to a folder. They will only be able to upload and see the names of the * items in a folder. They will not able to download or view any content. This role is only available to * enterprise accounts. */ - UPLOADER ("uploader"), + UPLOADER("uploader"), /** * The previewer-uploader role is a combination of previewer and uploader. A user with this access level will be @@ -374,7 +393,7 @@ public enum Role { * not be able to download, edit, or share, items in the folder. This role is only available to enterprise * accounts. */ - PREVIEWER_UPLOADER ("previewer uploader"), + PREVIEWER_UPLOADER("previewer uploader"), /** * The viewer-uploader role is a combination of viewer and uploader. A viewer-uploader has full read access to a @@ -382,7 +401,7 @@ public enum Role { * upload content to the folder. They will not be able to add tags, invite new collaborators, edit, or delete * items in the folder. This role is only available to enterprise accounts. */ - VIEWER_UPLOADER ("viewer uploader"), + VIEWER_UPLOADER("viewer uploader"), /** * The co-owner role has all of the functional read/write access that an editor does. This permission level has @@ -391,14 +410,14 @@ public enum Role { * manipulate the owner of the folder or transfer ownership to another user. This role is only available to * enterprise accounts. */ - CO_OWNER ("co-owner"), + CO_OWNER("co-owner"), /** * The owner role has all of the functional capabilities of a co-owner. However, they will be able to manipulate * the owner of the folder or transfer ownership to another user. This role is only available to enterprise * accounts. */ - OWNER ("owner"); + OWNER("owner"); private final String jsonValue; @@ -432,4 +451,32 @@ String toJSONString() { return this.jsonValue; } } + + /** + * Used to retrieve all collaborations associated with the item. + * + * @param api BoxAPIConnection from the associated file. + * @param fileID FileID of the assocyaed file + * @param pageSize page size for server pages of the Iterable + * @param fields the optional fields to retrieve. + * @return An iterable of BoxCollaboration.Info instances associated with the item. + */ + public static BoxResourceIterable getAllFileCollaborations(final BoxAPIConnection api, String fileID, + int pageSize, String... fields) { + QueryStringBuilder builder = new QueryStringBuilder(); + if (fields.length > 0) { + builder.appendParam("fields", fields); + } + return new BoxResourceIterable( + api, GET_ALL_FILE_COLLABORATIONS_URL.buildWithQuery(api.getBaseURL(), builder.toString(), fileID), + pageSize) { + + @Override + protected BoxCollaboration.Info factory(JsonObject jsonObject) { + String id = jsonObject.get("id").asString(); + return new BoxCollaboration(api, id).new Info(jsonObject); + } + + }; + } } diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 023489442..2be3d1219 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -23,7 +23,7 @@ * *

Unless otherwise noted, the methods in this class can throw an unchecked {@link BoxAPIException} (unchecked * meaning that the compiler won't force you to handle it) if an error occurs. If you wish to implement custom error - * handling for errors related to the Box REST API, you should capture this exception explicitly.

+ * handling for errors related to the Box REST API, you should capture this exception explicitly. */ @BoxResourceType("file") public class BoxFile extends BoxItem { @@ -32,10 +32,12 @@ public class BoxFile extends BoxItem { * An array of all possible file fields that can be requested when calling {@link #getInfo()}. */ public static final String[] ALL_FIELDS = {"type", "id", "sequence_id", "etag", "sha1", "name", - "description", "size", "path_collection", "created_at", "modified_at", "trashed_at", "purged_at", - "content_created_at", "content_modified_at", "created_by", "modified_by", "owned_by", "shared_link", "parent", - "item_status", "version_number", "comment_count", "permissions", "tags", "lock", "extension", "is_package", - "file_version", "collections", "watermark_info"}; + "description", "size", "path_collection", "created_at", "modified_at", + "trashed_at", "purged_at", "content_created_at", "content_modified_at", + "created_by", "modified_by", "owned_by", "shared_link", "parent", + "item_status", "version_number", "comment_count", "permissions", "tags", + "lock", "extension", "is_package", "file_version", "collections", + "watermark_info"}; /** * Used to specify what filetype to request for a file thumbnail. @@ -64,15 +66,17 @@ public enum ThumbnailFileType { private static final URLTemplate GET_THUMBNAIL_PNG_TEMPLATE = new URLTemplate("files/%s/thumbnail.png"); private static final URLTemplate GET_THUMBNAIL_JPG_TEMPLATE = new URLTemplate("files/%s/thumbnail.jpg"); private static final URLTemplate ADD_COLLABORATION_URL = new URLTemplate("collaborations"); - private static final URLTemplate GET_COLLABORATIONS_URL = new URLTemplate("folders/%s/collaborations"); + private static final URLTemplate GET_ALL_FILE_COLLABORATIONS_URL = new URLTemplate("files/%s/collaborations"); - private static final int BUFFER_SIZE = 8192; + private static final int BUFFER_SIZE = 8192; + private static final int GET_COLLABORATORS_PAGE_SIZE = 1000; /** * Constructs a BoxFile for a file with a given ID. - * @param api the API connection to be used by the file. - * @param id the ID of the file. + * + * @param api the API connection to be used by the file. + * @param id the ID of the file. */ public BoxFile(BoxAPIConnection api, String id) { super(api, id); @@ -88,7 +92,7 @@ protected URL getItemURL() { @Override public BoxSharedLink createSharedLink(BoxSharedLink.Access access, Date unshareDate, - BoxSharedLink.Permissions permissions) { + BoxSharedLink.Permissions permissions) { BoxSharedLink sharedLink = new BoxSharedLink(access, unshareDate, permissions); Info info = new Info(); @@ -101,10 +105,8 @@ public BoxSharedLink createSharedLink(BoxSharedLink.Access access, Date unshareD /** * Adds new {@link BoxWebHook} to this {@link BoxFile}. * - * @param address - * {@link BoxWebHook.Info#getAddress()} - * @param triggers - * {@link BoxWebHook.Info#getTriggers()} + * @param address {@link BoxWebHook.Info#getAddress()} + * @param triggers {@link BoxWebHook.Info#getTriggers()} * @return created {@link BoxWebHook.Info} */ public BoxWebHook.Info addWebHook(URL address, BoxWebHook.Trigger... triggers) { @@ -114,10 +116,11 @@ public BoxWebHook.Info addWebHook(URL address, BoxWebHook.Trigger... triggers) { /** * Adds a comment to this file. The message can contain @mentions by using the string @[userid:username] anywhere * within the message, where userid and username are the ID and username of the person being mentioned. - * @see the tagged_message field - * for including @mentions. - * @param message the comment's message. + * + * @param message the comment's message. * @return information about the newly added comment. + * @see the tagged_message field + * for including @mentions. */ public BoxComment.Info addComment(String message) { JsonObject itemJSON = new JsonObject(); @@ -144,9 +147,10 @@ public BoxComment.Info addComment(String message) { /** * Adds a new task to this file. The task can have an optional message to include, and a due date. - * @param action the action the task assignee will be prompted to do. + * + * @param action the action the task assignee will be prompted to do. * @param message an optional message to include with the task. - * @param dueAt the day at which this task is due. + * @param dueAt the day at which this task is due. * @return information about the newly added task. */ public BoxTask.Info addTask(BoxTask.Action action, String message, Date dueAt) { @@ -180,6 +184,7 @@ public BoxTask.Info addTask(BoxTask.Action action, String message, Date dueAt) { * Gets an expiring URL for downloading a file directly from Box. This can be user, * for example, for sending as a redirect to a browser to cause the browser * to download the file directly from Box. + * * @return the temporary download URL */ public URL getDownloadURL() { @@ -194,6 +199,7 @@ public URL getDownloadURL() { /** * Downloads the contents of this file to a given OutputStream. + * * @param output the stream to where the file will be written. */ public void download(OutputStream output) { @@ -202,6 +208,7 @@ public void download(OutputStream output) { /** * Downloads the contents of this file to a given OutputStream while reporting the progress to a ProgressListener. + * * @param output the stream to where the file will be written. * @param listener a listener for monitoring the download's progress. */ @@ -227,6 +234,7 @@ public void download(OutputStream output, ProgressListener listener) { /** * Downloads a part of this file's contents, starting at specified byte offset. + * * @param output the stream to where the file will be written. * @param offset the byte offset at which to start the download. */ @@ -236,6 +244,7 @@ public void downloadRange(OutputStream output, long offset) { /** * Downloads a part of this file's contents, starting at rangeStart and stopping at rangeEnd. + * * @param output the stream to where the file will be written. * @param rangeStart the byte offset at which to start the download. * @param rangeEnd the byte offset at which to stop the download. @@ -247,6 +256,7 @@ public void downloadRange(OutputStream output, long rangeStart, long rangeEnd) { /** * Downloads a part of this file's contents, starting at rangeStart and stopping at rangeEnd, while reporting the * progress to a ProgressListener. + * * @param output the stream to where the file will be written. * @param rangeStart the byte offset at which to start the download. * @param rangeEnd the byte offset at which to stop the download. @@ -257,7 +267,7 @@ public void downloadRange(OutputStream output, long rangeStart, long rangeEnd, P BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); if (rangeEnd > 0) { request.addHeader("Range", String.format("bytes=%s-%s", Long.toString(rangeStart), - Long.toString(rangeEnd))); + Long.toString(rangeEnd))); } else { request.addHeader("Range", String.format("bytes=%s-", Long.toString(rangeStart))); } @@ -343,6 +353,7 @@ public BoxItem.Info move(BoxFolder destination, String newName) { /** * Renames this file. + * * @param newName the new name of the file. */ public void rename(String newName) { @@ -383,8 +394,8 @@ public BoxFile.Info getInfo(String... fields) { * changed:

* *
BoxFile file = new File(api, id);
-     *BoxFile.Info info = file.getInfo();
-     *file.updateInfo(info);
+ * BoxFile.Info info = file.getInfo(); + * file.updateInfo(info); * * @param info the updated info. */ @@ -400,6 +411,7 @@ public void updateInfo(BoxFile.Info info) { /** * Gets any previous versions of this file. Note that only users with premium accounts will be able to retrieve * previous versions of their files. + * * @return a list of previous file versions. */ public Collection getVersions() { @@ -419,9 +431,10 @@ public Collection getVersions() { /** * Checks if the file can be successfully uploaded by using the preflight check. - * @param name the name to give the uploaded file or null to use existing name. - * @param fileSize the size of the file used for account capacity calculations. - * @param parentID the ID of the parent folder that the new version is being uploaded to. + * + * @param name the name to give the uploaded file or null to use existing name. + * @param fileSize the size of the file used for account capacity calculations. + * @param parentID the ID of the parent folder that the new version is being uploaded to. */ public void canUploadVersion(String name, long fileSize, String parentID) { URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); @@ -446,6 +459,7 @@ public void canUploadVersion(String name, long fileSize, String parentID) { /** * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts * will be able to view and recover previous versions of the file. + * * @param fileContent a stream containing the new file contents. */ public void uploadVersion(InputStream fileContent) { @@ -455,9 +469,9 @@ public void uploadVersion(InputStream fileContent) { /** * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts * will be able to view and recover previous versions of the file. + * * @param fileContent a stream containing the new file contents. * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents. - * */ public void uploadVersion(InputStream fileContent, String fileContentSHA1) { this.uploadVersion(fileContent, fileContentSHA1, null); @@ -466,6 +480,7 @@ public void uploadVersion(InputStream fileContent, String fileContentSHA1) { /** * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts * will be able to view and recover previous versions of the file. + * * @param fileContent a stream containing the new file contents. * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents. * @param modified the date that the new version was modified. @@ -478,10 +493,11 @@ public void uploadVersion(InputStream fileContent, String fileContentSHA1, Date * Uploads a new version of this file, replacing the current version, while reporting the progress to a * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions * of the file. - * @param fileContent a stream containing the new file contents. - * @param modified the date that the new version was modified. - * @param fileSize the size of the file used for determining the progress of the upload. - * @param listener a listener for monitoring the upload's progress. + * + * @param fileContent a stream containing the new file contents. + * @param modified the date that the new version was modified. + * @param fileSize the size of the file used for determining the progress of the upload. + * @param listener a listener for monitoring the upload's progress. */ public void uploadVersion(InputStream fileContent, Date modified, long fileSize, ProgressListener listener) { this.uploadVersion(fileContent, null, modified, fileSize, listener); @@ -491,6 +507,7 @@ public void uploadVersion(InputStream fileContent, Date modified, long fileSize, * Uploads a new version of this file, replacing the current version, while reporting the progress to a * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions * of the file. + * * @param fileContent a stream containing the new file contents. * @param fileContentSHA1 the SHA1 hash of the file contents. will be sent along in the Content-MD5 header * @param modified the date that the new version was modified. @@ -528,6 +545,7 @@ public void uploadVersion(InputStream fileContent, String fileContentSHA1, Date /** * Gets an expiring URL for creating an embedded preview session. The URL will expire after 60 seconds and the * preview session will expire after 60 minutes. + * * @return the expiring preview link */ public URL getPreviewLink() { @@ -541,11 +559,12 @@ public URL getPreviewLink() { * Retrieves a thumbnail, or smaller image representation, of this file. Sizes of 32x32, 64x64, 128x128, * and 256x256 can be returned in the .png format and sizes of 32x32, 94x94, 160x160, and 320x320 can be returned * in the .jpg format. - * @param fileType either PNG of JPG - * @param minWidth minimum width - * @param minHeight minimum height - * @param maxWidth maximum width - * @param maxHeight maximum height + * + * @param fileType either PNG of JPG + * @param minWidth minimum width + * @param minHeight minimum height + * @param maxWidth maximum width + * @param maxHeight maximum height * @return the byte array of the thumbnail image */ public byte[] getThumbnail(ThumbnailFileType fileType, int minWidth, int minHeight, int maxWidth, int maxHeight) { @@ -588,6 +607,7 @@ public byte[] getThumbnail(ThumbnailFileType fileType, int minWidth, int minHeig /** * Gets a list of any comments on this file. + * * @return a list of comments on this file. */ public List getComments() { @@ -611,6 +631,7 @@ public List getComments() { /** * Gets a list of any tasks on this file. + * * @return a list of tasks on this file. */ public List getTasks() { @@ -634,6 +655,7 @@ public List getTasks() { /** * Creates metadata on this file in the global properties template. + * * @param metadata The new metadata values. * @return the metadata returned from the server. */ @@ -643,6 +665,7 @@ public Metadata createMetadata(Metadata metadata) { /** * Creates metadata on this file in the specified template type. + * * @param typeName the metadata template type name. * @param metadata the new metadata values. * @return the metadata returned from the server. @@ -654,8 +677,9 @@ public Metadata createMetadata(String typeName, Metadata metadata) { /** * Creates metadata on this file in the specified template type. + * * @param typeName the metadata template type name. - * @param scope the metadata scope (global or enterprise). + * @param scope the metadata scope (global or enterprise). * @param metadata the new metadata values. * @return the metadata returned from the server. */ @@ -670,6 +694,7 @@ public Metadata createMetadata(String typeName, String scope, Metadata metadata) /** * Locks a file. + * * @param expiresAt expiration date of the lock. * @return the lock returned from the server. */ @@ -679,7 +704,8 @@ public BoxLock lock(Date expiresAt) { /** * Locks a file. - * @param expiresAt expiration date of the lock. + * + * @param expiresAt expiration date of the lock. * @param isDownloadPrevented is downloading of file prevented when locked. * @return the lock returned from the server. */ @@ -723,15 +749,17 @@ public void unlock() { /** * Used to retrieve all metadata associated with the file. + * * @param fields the optional fields to retrieve. * @return An iterable of metadata instances associated with the file. */ - public Iterable getAllMetadata(String ... fields) { + public Iterable getAllMetadata(String... fields) { return Metadata.getAllMetadata(this, fields); } /** * Gets the file properties metadata. + * * @return the metadata returned from the server. */ public Metadata getMetadata() { @@ -740,6 +768,7 @@ public Metadata getMetadata() { /** * Gets the file metadata of specified template type. + * * @param typeName the metadata template type name. * @return the metadata returned from the server. */ @@ -750,8 +779,9 @@ public Metadata getMetadata(String typeName) { /** * Gets the file metadata of specified template type. + * * @param typeName the metadata template type name. - * @param scope the metadata scope (global or enterprise). + * @param scope the metadata scope (global or enterprise). * @return the metadata returned from the server. */ public Metadata getMetadata(String typeName, String scope) { @@ -763,6 +793,7 @@ public Metadata getMetadata(String typeName, String scope) { /** * Updates the file metadata. + * * @param metadata the new metadata values. * @return the metadata returned from the server. */ @@ -775,7 +806,7 @@ public Metadata updateMetadata(Metadata metadata) { } URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), - scope, metadata.getTemplateName()); + scope, metadata.getTemplateName()); BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT"); request.addHeader("Content-Type", "application/json-patch+json"); request.setBody(metadata.getPatch()); @@ -792,6 +823,7 @@ public void deleteMetadata() { /** * Deletes the file metadata of specified template type. + * * @param typeName the metadata template type name. */ public void deleteMetadata(String typeName) { @@ -801,8 +833,9 @@ public void deleteMetadata(String typeName) { /** * Deletes the file metadata of specified template type. + * * @param typeName the metadata template type name. - * @param scope the metadata scope (global or enterprise). + * @param scope the metadata scope (global or enterprise). */ public void deleteMetadata(String typeName, String scope) { URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), scope, typeName); @@ -813,6 +846,7 @@ public void deleteMetadata(String typeName, String scope) { /** * Used to retrieve the watermark for the file. * If the file does not have a watermark applied to it, a 404 Not Found will be returned by API. + * * @param fields the fields to retrieve. * @return the watermark associated with the file. */ @@ -822,6 +856,7 @@ public BoxWatermark getWatermark(String... fields) { /** * Used to apply or update the watermark for the file. + * * @return the watermark associated with the file. */ public BoxWatermark applyWatermark() { @@ -883,7 +918,8 @@ public Info() { /** * Constructs an Info object by parsing information from a JSON string. - * @param json the JSON string to parse. + * + * @param json the JSON string to parse. */ public Info(String json) { super(json); @@ -891,7 +927,8 @@ public Info(String json) { /** * Constructs an Info object using an already parsed JSON object. - * @param jsonObject the parsed JSON object. + * + * @param jsonObject the parsed JSON object. */ Info(JsonObject jsonObject) { super(jsonObject); @@ -904,6 +941,7 @@ public BoxFile getResource() { /** * Gets the SHA1 hash of the file. + * * @return the SHA1 hash of the file. */ public String getSha1() { @@ -912,6 +950,7 @@ public String getSha1() { /** * Gets the lock of the file. + * * @return the lock of the file. */ public BoxLock getLock() { @@ -920,6 +959,7 @@ public BoxLock getLock() { /** * Gets the current version number of the file. + * * @return the current version number of the file. */ public String getVersionNumber() { @@ -928,6 +968,7 @@ public String getVersionNumber() { /** * Gets the number of comments on the file. + * * @return the number of comments on the file. */ public long getCommentCount() { @@ -936,6 +977,7 @@ public long getCommentCount() { /** * Gets the permissions that the current user has on the file. + * * @return the permissions that the current user has on the file. */ public EnumSet getPermissions() { @@ -944,6 +986,7 @@ public EnumSet getPermissions() { /** * Gets the extension suffix of the file, excluding the dot. + * * @return the extension of the file. */ public String getExtension() { @@ -952,6 +995,7 @@ public String getExtension() { /** * Gets whether or not the file is an OSX package. + * * @return true if the file is an OSX package; otherwise false. */ public boolean getIsPackage() { @@ -960,6 +1004,7 @@ public boolean getIsPackage() { /** * Gets the current version details of the file. + * * @return the current version details of the file. */ public BoxFileVersion getVersion() { @@ -968,6 +1013,7 @@ public BoxFileVersion getVersion() { /** * Gets the current expiring preview link. + * * @return the expiring preview link */ public URL getPreviewLink() { @@ -976,6 +1022,7 @@ public URL getPreviewLink() { /** * Gets flag indicating whether this file is Watermarked. + * * @return whether the file is watermarked or not */ public boolean getIsWatermarked() { @@ -1064,42 +1111,42 @@ public enum Permission { /** * The user can download the file. */ - CAN_DOWNLOAD ("can_download"), + CAN_DOWNLOAD("can_download"), /** * The user can upload new versions of the file. */ - CAN_UPLOAD ("can_upload"), + CAN_UPLOAD("can_upload"), /** * The user can rename the file. */ - CAN_RENAME ("can_rename"), + CAN_RENAME("can_rename"), /** * The user can delete the file. */ - CAN_DELETE ("can_delete"), + CAN_DELETE("can_delete"), /** * The user can share the file. */ - CAN_SHARE ("can_share"), + CAN_SHARE("can_share"), /** * The user can set the access level for shared links to the file. */ - CAN_SET_SHARE_ACCESS ("can_set_share_access"), + CAN_SET_SHARE_ACCESS("can_set_share_access"), /** * The user can preview the file. */ - CAN_PREVIEW ("can_preview"), + CAN_PREVIEW("can_preview"), /** * The user can comment on the file. */ - CAN_COMMENT ("can_comment"); + CAN_COMMENT("can_comment"); private final String jsonValue; @@ -1115,6 +1162,7 @@ String toJSONValue() { return this.jsonValue; } } + private BoxCollaboration.Info collaborate(JsonObject accessibleByField, BoxCollaboration.Role role) { BoxAPIConnection api = this.getAPI(); URL url = ADD_COLLABORATION_URL.build(api.getBaseURL()); @@ -1137,11 +1185,13 @@ private BoxCollaboration.Info collaborate(JsonObject accessibleByField, BoxColla BoxCollaboration.Info info = newCollaboration.new Info(responseJSON); return info; } + /** * Adds a collaborator to this file. - * @param collaborator the collaborator to add. - * @param role the role of the collaborator. - * @return info about the new collaboration. + * + * @param collaborator the collaborator to add. + * @param role the role of the collaborator. + * @return info about the new collaboration. */ public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role) { JsonObject accessibleByField = new JsonObject(); @@ -1162,9 +1212,10 @@ public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollab /** * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't already have a Box * account. - * @param email the email address of the collaborator to add. - * @param role the role of the collaborator. - * @return info about the new collaboration. + * + * @param email the email address of the collaborator to add. + * @param role the role of the collaborator. + * @return info about the new collaboration. */ public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role) { JsonObject accessibleByField = new JsonObject(); @@ -1174,6 +1225,15 @@ public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role rol return this.collaborate(accessibleByField, role); } + /** + * Used to retrieve all collaborations associated with the item. + * + * @param fields the optional fields to retrieve. + * @return An iterable of metadata instances associated with the item. + */ + public BoxResourceIterable getAllFileCollaborations(String... fields) { + return BoxCollaboration.getAllFileCollaborations(this.getAPI(), this.getID(), + GET_COLLABORATORS_PAGE_SIZE, fields); - + } } diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index d07ee7039..0caab0fc7 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -127,6 +127,82 @@ private BoxCollaboration.Info collaborate(JsonObject accessibleByField, BoxColla BoxCollaboration.Info info = newCollaboration.new Info(responseJSON); return info; } + /** + * Adds a collaborator to this folder. + * @param collaborator the collaborator to add. + * @param role the role of the collaborator. + * @param notify the user/group should receive email notification of the collaboration or not. + * @param canViewPath the view path collaboration feature is enabled or not. + * View path collaborations allow the invitee to see the entire ancestral path to the associated folder. + * The user will not gain privileges in any ancestral folder. + * @return info about the new collaboration. + */ + public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role, + Boolean notify, Boolean canViewPath) { + JsonObject accessibleByField = new JsonObject(); + accessibleByField.add("id", collaborator.getID()); + + if (collaborator instanceof BoxUser) { + accessibleByField.add("type", "user"); + } else if (collaborator instanceof BoxGroup) { + accessibleByField.add("type", "group"); + } else { + throw new IllegalArgumentException("The given collaborator is of an unknown type."); + } + + return this.collaborate(accessibleByField, role, notify, canViewPath); + } + + /** + * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't already have a Box + * account. + * @param email the email address of the collaborator to add. + * @param role the role of the collaborator. + * @param notify the user/group should receive email notification of the collaboration or not. + * @param canViewPath the view path collaboration feature is enabled or not. + * View path collaborations allow the invitee to see the entire ancestral path to the associated folder. + * The user will not gain privileges in any ancestral folder. + * @return info about the new collaboration. + */ + public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role, + Boolean notify, Boolean canViewPath) { + JsonObject accessibleByField = new JsonObject(); + accessibleByField.add("login", email); + accessibleByField.add("type", "user"); + + return this.collaborate(accessibleByField, role, notify, canViewPath); + } + + private BoxCollaboration.Info collaborate(JsonObject accessibleByField, BoxCollaboration.Role role, + Boolean notify, Boolean canViewPath) { + BoxAPIConnection api = this.getAPI(); + URL url = ADD_COLLABORATION_URL.build(api.getBaseURL()); + + JsonObject itemField = new JsonObject(); + itemField.add("id", this.getID()); + itemField.add("type", "folder"); + + JsonObject requestJSON = new JsonObject(); + requestJSON.add("item", itemField); + requestJSON.add("accessible_by", accessibleByField); + requestJSON.add("role", role.toJSONString()); + if (canViewPath != null) { + requestJSON.add("can_view_path", canViewPath.booleanValue()); + } + + BoxJSONRequest request = new BoxJSONRequest(api, url, "POST"); + if (notify != null) { + request.addHeader("notify", notify.toString()); + } + + request.setBody(requestJSON.toString()); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); + + BoxCollaboration newCollaboration = new BoxCollaboration(api, responseJSON.get("id").asString()); + BoxCollaboration.Info info = newCollaboration.new Info(responseJSON); + return info; + } @Override public BoxSharedLink createSharedLink(BoxSharedLink.Access access, Date unshareDate, @@ -165,6 +241,8 @@ public Collection getCollaborations() { return collaborations; } + + @Override public BoxFolder.Info getInfo() { URL url = FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); diff --git a/src/main/java/com/box/sdk/BoxResourceIterable.java b/src/main/java/com/box/sdk/BoxResourceIterable.java index 9f840e8e7..811781f7b 100644 --- a/src/main/java/com/box/sdk/BoxResourceIterable.java +++ b/src/main/java/com/box/sdk/BoxResourceIterable.java @@ -157,7 +157,7 @@ public boolean hasNext() { if (this.pageCursor < this.page.size()) { return true; } - if (this.markerNext == null) { + if (this.markerNext == null || this.markerNext.isEmpty()) { return false; } this.loadNextPage(); diff --git a/src/test/java/com/box/sdk/BoxCollaborationTest.java b/src/test/java/com/box/sdk/BoxCollaborationTest.java index fd325d4ad..c612f27ea 100644 --- a/src/test/java/com/box/sdk/BoxCollaborationTest.java +++ b/src/test/java/com/box/sdk/BoxCollaborationTest.java @@ -1,15 +1,21 @@ package com.box.sdk; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.logging.Logger; + import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import org.junit.Test; import org.junit.experimental.categories.Category; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.util.logging.Logger; public class BoxCollaborationTest { @Test @@ -33,6 +39,14 @@ public void updateInfoSucceeds() { collab.updateInfo(collabInfo); assertThat(collabInfo.getRole(), is(equalTo(newRole))); + Collection collabCollection = folder.getCollaborations(); + + assertEquals(collabCollection.size(), 1); + + Iterator collabs = collabCollection.iterator(); + BoxCollaboration.Info remoteCollab = collabs.next(); + assertThat(remoteCollab.getRole(), is(equalTo(newRole))); + folder.delete(false); } @@ -58,7 +72,7 @@ public void deleteSucceeds() { @Test @Category(IntegrationTestDebug.class) public void singleFileCollabSucceeds() { - Logger logger = TestConfig.enableLogger("FINE"); + HashMap collabsMap = new HashMap(); BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); BoxFolder rootFolder = BoxFolder.getRootFolder(api); String fileName = "[singleFileCollabSucceeds] Test File.txt"; @@ -73,6 +87,8 @@ public void singleFileCollabSucceeds() { BoxCollaboration.Info collabInfo = uploadedFile.collaborate(collaboratorLogin, originalRole); + collabsMap.put(collabInfo.getID(), collabInfo); + assertThat(collabInfo.getRole(), is(equalTo(originalRole))); BoxCollaboration collab = collabInfo.getResource(); @@ -81,7 +97,57 @@ public void singleFileCollabSucceeds() { assertThat(collabInfo.getRole(), is(equalTo(newRole))); + BoxCollaboration remoteCollab = new BoxCollaboration(api, collab.getID()); + BoxCollaboration.Info remoteInfo = remoteCollab.getInfo(); + assertThat(remoteInfo.getRole(), is(equalTo(newRole))); + assertThat(remoteInfo.getCreatedBy().getID(), is(collabInfo.getCreatedBy().getID())); + + + BoxCollaboration.Info collab2Info = uploadedFile.collaborate("davidsmaynard@gmail.com", originalRole); + + collabsMap.put(collab2Info.getID(), collab2Info); + + BoxResourceIterable collabs = uploadedFile.getAllFileCollaborations(); + Iterator collabIterator = collabs.iterator(); + int numCollabs = 0; + + while (collabIterator.hasNext() && (numCollabs < 5)) { + numCollabs++; + BoxCollaboration.Info fileCollabInfo = collabIterator.next(); + + BoxCollaboration.Info localFileCollabInfor = collabsMap.get(fileCollabInfo.getID()); + + assertEquals(fileCollabInfo.getID(), localFileCollabInfor.getID()); + assertEquals(fileCollabInfo.getCreatedBy().getID(), localFileCollabInfor.getCreatedBy().getID()); + assertEquals(fileCollabInfo.getCreatedBy().getName(), localFileCollabInfor.getCreatedBy().getName()); + + assertEquals(fileCollabInfo.getAccessibleBy().getID(), localFileCollabInfor.getAccessibleBy().getID()); + assertEquals(fileCollabInfo.getAccessibleBy().getName(), localFileCollabInfor.getAccessibleBy().getName()); + + assertEquals(fileCollabInfo.getRole(), localFileCollabInfor.getRole()); + assertEquals(fileCollabInfo.getStatus(), localFileCollabInfor.getStatus()); + + } + + BoxCollaboration.Info colInfo = collabIterator.next(); + + assertThat(colInfo.getID(), is(equalTo(collab2Info.getID()))); + assertEquals(colInfo.getID(), collab2Info.getID()); + + assertEquals(colInfo.getID(), collabInfo.getID()); + + assertEquals(collabs.iterator().hasNext(), true); + + colInfo = collabIterator.next(); + + assertEquals(colInfo.getID(), collabInfo.getID()); + + assertEquals(collabs.iterator().hasNext(), false); + assertEquals(2, numCollabs); + uploadedFile.delete(); + + } } diff --git a/src/test/java/com/box/sdk/BoxFolderTest.java b/src/test/java/com/box/sdk/BoxFolderTest.java index fdc7d75d3..c2d296574 100644 --- a/src/test/java/com/box/sdk/BoxFolderTest.java +++ b/src/test/java/com/box/sdk/BoxFolderTest.java @@ -1,52 +1,31 @@ package com.box.sdk; +import com.eclipsesource.json.JsonArray; +import com.eclipsesource.json.JsonObject; +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.junit.WireMockRule; + + import java.io.ByteArrayInputStream; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.Collection; -import java.util.Date; -import java.util.EnumSet; -import java.util.Iterator; -import java.util.List; -import java.util.Scanner; -import java.util.TimeZone; +import java.util.*; +import java.util.logging.Logger; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.isEmptyOrNullString; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; import static org.skyscreamer.jsonassert.JSONCompareMode.LENIENT; - import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.containing; -import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.post; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; - -import com.eclipsesource.json.JsonArray; -import com.eclipsesource.json.JsonObject; -import com.github.tomakehurst.wiremock.client.WireMock; -import com.github.tomakehurst.wiremock.junit.WireMockRule; /** * {@link BoxFolder} related tests. @@ -846,6 +825,8 @@ public void moveFolderSucceeds() { @Test @Category(IntegrationTest.class) public void renameFolderSucceeds() { + // For some reason the following call causes a failure (connection already closed) + // Logger logger = TestConfig.enableLogger("FINE"); BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); final String originalName = "[renameFolderSucceeds] Original Name"; final String newName = "[renameFolderSucceeds] New Name"; @@ -879,12 +860,41 @@ public void addCollaboratorSucceeds() { folder.delete(false); } + @Test + @Category(IntegrationTest.class) + public void addCollaborationsWithAttributesSucceeds() { + // Logger logger = TestConfig.enableLogger("FINE"); + BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); + String folderName = "[getCollaborationsSucceeds] Test Folder"; + String collaboratorLogin = "karthik2001123@yahoo.com"; + BoxCollaboration.Role collaboratorRole = BoxCollaboration.Role.CO_OWNER; + + BoxFolder rootFolder = BoxFolder.getRootFolder(api); + BoxFolder folder = rootFolder.createFolder(folderName).getResource(); + BoxCollaboration.Info collabInfo = folder.collaborate(collaboratorLogin, collaboratorRole, true, true); + String collabID = collabInfo.getID(); + + collaboratorRole = BoxCollaboration.Role.VIEWER; + collaboratorLogin = "davidsmaynard@gmail.com"; + BoxCollaboration.Info collabInfo2 = folder.collaborate(collaboratorLogin, collaboratorRole, true, true); + + collaboratorLogin = TestConfig.getCollaborator(); + BoxCollaboration.Info collabInfo3 = folder.collaborate(collaboratorLogin, collaboratorRole, true, true); + + + Collection collaborations = folder.getCollaborations(); + + assertThat(collaborations, hasSize(3)); + assertThat(collaborations, hasItem(Matchers.hasProperty("ID", equalTo(collabID)))); + + folder.delete(false); + } @Test @Category(IntegrationTest.class) public void getCollaborationsHasCorrectCollaborations() { BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); - String folderName = "[getCollaborationsSucceeds] Test Folder"; + String folderName = "[getCollaborationsHasCorrectCollaborations] Test Folder"; String collaboratorLogin = TestConfig.getCollaborator(); BoxCollaboration.Role collaboratorRole = BoxCollaboration.Role.CO_OWNER; From 327004a99f7a884aadf10ed56a5a64cb6fde4c56 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Tue, 14 Mar 2017 17:57:22 -0700 Subject: [PATCH 082/119] Fixing checkstyle issues in tests --- config/checkstyle/suppressions.xml | 4 +++- src/test/java/com/box/sdk/BoxCollaborationTest.java | 1 - src/test/java/com/box/sdk/BoxFolderTest.java | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml index 3913785c9..92ee3f784 100644 --- a/config/checkstyle/suppressions.xml +++ b/config/checkstyle/suppressions.xml @@ -8,5 +8,7 @@ + files="example.*\.java"/> + diff --git a/src/test/java/com/box/sdk/BoxCollaborationTest.java b/src/test/java/com/box/sdk/BoxCollaborationTest.java index c612f27ea..fbe11b3c7 100644 --- a/src/test/java/com/box/sdk/BoxCollaborationTest.java +++ b/src/test/java/com/box/sdk/BoxCollaborationTest.java @@ -6,7 +6,6 @@ import java.util.Collection; import java.util.HashMap; import java.util.Iterator; -import java.util.logging.Logger; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; diff --git a/src/test/java/com/box/sdk/BoxFolderTest.java b/src/test/java/com/box/sdk/BoxFolderTest.java index c2d296574..c0b12ba68 100644 --- a/src/test/java/com/box/sdk/BoxFolderTest.java +++ b/src/test/java/com/box/sdk/BoxFolderTest.java @@ -14,7 +14,7 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; -import java.util.logging.Logger; + import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.*; @@ -31,6 +31,7 @@ * {@link BoxFolder} related tests. */ public class BoxFolderTest { + @SuppressWarnings("checkstyle:wrongOrder") @Rule public final WireMockRule wireMockRule = new WireMockRule(8080); From 600baea6bce5609258b5ec9e1779e026dec45112 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Mon, 20 Mar 2017 16:16:16 -0700 Subject: [PATCH 083/119] Fixed bug in singleFIleCollabTest and premature disconect in Folder Rename --- src/main/java/com/box/sdk/BoxFolder.java | 4 ++-- .../com/box/sdk/BoxCollaborationTest.java | 21 +++---------------- src/test/java/com/box/sdk/BoxFolderTest.java | 2 -- 3 files changed, 5 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index 0caab0fc7..444989b1d 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -372,8 +372,8 @@ public void rename(String newName) { updateInfo.add("name", newName); request.setBody(updateInfo.toString()); - BoxAPIResponse response = request.send(); - response.disconnect(); + BoxJSONResponse response = (BoxJSONResponse)request.send(); + response.getJSON(); } /** diff --git a/src/test/java/com/box/sdk/BoxCollaborationTest.java b/src/test/java/com/box/sdk/BoxCollaborationTest.java index fbe11b3c7..4c288e740 100644 --- a/src/test/java/com/box/sdk/BoxCollaborationTest.java +++ b/src/test/java/com/box/sdk/BoxCollaborationTest.java @@ -69,7 +69,7 @@ public void deleteSucceeds() { } @Test - @Category(IntegrationTestDebug.class) + @Category(IntegrationTest.class) public void singleFileCollabSucceeds() { HashMap collabsMap = new HashMap(); BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); @@ -110,10 +110,9 @@ public void singleFileCollabSucceeds() { Iterator collabIterator = collabs.iterator(); int numCollabs = 0; - while (collabIterator.hasNext() && (numCollabs < 5)) { + while (collabIterator.hasNext()) { numCollabs++; BoxCollaboration.Info fileCollabInfo = collabIterator.next(); - BoxCollaboration.Info localFileCollabInfor = collabsMap.get(fileCollabInfo.getID()); assertEquals(fileCollabInfo.getID(), localFileCollabInfor.getID()); @@ -125,23 +124,9 @@ public void singleFileCollabSucceeds() { assertEquals(fileCollabInfo.getRole(), localFileCollabInfor.getRole()); assertEquals(fileCollabInfo.getStatus(), localFileCollabInfor.getStatus()); - } - BoxCollaboration.Info colInfo = collabIterator.next(); - - assertThat(colInfo.getID(), is(equalTo(collab2Info.getID()))); - assertEquals(colInfo.getID(), collab2Info.getID()); - - assertEquals(colInfo.getID(), collabInfo.getID()); - - assertEquals(collabs.iterator().hasNext(), true); - - colInfo = collabIterator.next(); - - assertEquals(colInfo.getID(), collabInfo.getID()); - - assertEquals(collabs.iterator().hasNext(), false); + assertEquals(collabIterator.hasNext(), false); assertEquals(2, numCollabs); uploadedFile.delete(); diff --git a/src/test/java/com/box/sdk/BoxFolderTest.java b/src/test/java/com/box/sdk/BoxFolderTest.java index c0b12ba68..beab027c6 100644 --- a/src/test/java/com/box/sdk/BoxFolderTest.java +++ b/src/test/java/com/box/sdk/BoxFolderTest.java @@ -826,8 +826,6 @@ public void moveFolderSucceeds() { @Test @Category(IntegrationTest.class) public void renameFolderSucceeds() { - // For some reason the following call causes a failure (connection already closed) - // Logger logger = TestConfig.enableLogger("FINE"); BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); final String originalName = "[renameFolderSucceeds] Original Name"; final String newName = "[renameFolderSucceeds] New Name"; From 0e2d3aa11d13d5fddf54e69a87ed404b06204537 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Mon, 20 Mar 2017 17:01:14 -0700 Subject: [PATCH 084/119] Fix chaeckstyle whitespace issue. --- src/main/java/com/box/sdk/BoxFolder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index 444989b1d..c3e560c1d 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -372,7 +372,7 @@ public void rename(String newName) { updateInfo.add("name", newName); request.setBody(updateInfo.toString()); - BoxJSONResponse response = (BoxJSONResponse)request.send(); + BoxJSONResponse response = (BoxJSONResponse) request.send(); response.getJSON(); } From 22ecdba69e68820d32f3366a6886751772d4f586 Mon Sep 17 00:00:00 2001 From: David Maynard Date: Tue, 21 Mar 2017 15:34:08 -0700 Subject: [PATCH 085/119] Removing extraneous blank lines --- src/main/java/com/box/sdk/BoxCollaboration.java | 1 - src/main/java/com/box/sdk/BoxFile.java | 1 - src/test/java/com/box/sdk/BoxCollaborationTest.java | 4 ---- 3 files changed, 6 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxCollaboration.java b/src/main/java/com/box/sdk/BoxCollaboration.java index 7c1080eb6..0fa86e36c 100644 --- a/src/main/java/com/box/sdk/BoxCollaboration.java +++ b/src/main/java/com/box/sdk/BoxCollaboration.java @@ -476,7 +476,6 @@ protected BoxCollaboration.Info factory(JsonObject jsonObject) { String id = jsonObject.get("id").asString(); return new BoxCollaboration(api, id).new Info(jsonObject); } - }; } } diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 2be3d1219..478c53f48 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -1204,7 +1204,6 @@ public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollab } else { throw new IllegalArgumentException("The given collaborator is of an unknown type."); } - return this.collaborate(accessibleByField, role); } diff --git a/src/test/java/com/box/sdk/BoxCollaborationTest.java b/src/test/java/com/box/sdk/BoxCollaborationTest.java index 4c288e740..ffff8f2fc 100644 --- a/src/test/java/com/box/sdk/BoxCollaborationTest.java +++ b/src/test/java/com/box/sdk/BoxCollaborationTest.java @@ -128,10 +128,6 @@ public void singleFileCollabSucceeds() { assertEquals(collabIterator.hasNext(), false); assertEquals(2, numCollabs); - uploadedFile.delete(); - - } - } From 8b50a0933f0bf1821a0c1f717e72fb05f50ef8cd Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Thu, 23 Feb 2017 10:42:21 -0800 Subject: [PATCH 086/119] Inital check-in for supercharged file upload create session implementation --- .../java/com/box/sdk/BoxAPIConnection.java | 19 +++ src/main/java/com/box/sdk/BoxFile.java | 47 +++++- .../com/box/sdk/BoxFileUploadSession.java | 146 ++++++++++++++++++ src/main/java/com/box/sdk/BoxFolder.java | 19 +++ src/test/java/com/box/sdk/BoxFileTest.java | 48 ++++++ src/test/java/com/box/sdk/BoxFolderTest.java | 19 +++ 6 files changed, 293 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/box/sdk/BoxFileUploadSession.java diff --git a/src/main/java/com/box/sdk/BoxAPIConnection.java b/src/main/java/com/box/sdk/BoxAPIConnection.java index 2c54aeb6e..10bf2f145 100644 --- a/src/main/java/com/box/sdk/BoxAPIConnection.java +++ b/src/main/java/com/box/sdk/BoxAPIConnection.java @@ -26,6 +26,7 @@ public class BoxAPIConnection { private static final String TOKEN_URL_STRING = "https://api.box.com/oauth2/token"; private static final String DEFAULT_BASE_URL = "https://api.box.com/2.0/"; private static final String DEFAULT_BASE_UPLOAD_URL = "https://upload.box.com/api/2.0/"; + private static final String DEFAULT_BASE_UPLOAD_SESSION_URL = "https://upload.app.box.com/api/2.1/"; /** * The amount of buffer time, in milliseconds, to use when determining if an access token should be refreshed. For @@ -52,6 +53,7 @@ public class BoxAPIConnection { private String tokenURL; private String baseURL; private String baseUploadURL; + private String baseUploadSessionURL; private boolean autoRefresh; private int maxRequestAttempts; private List listeners; @@ -80,6 +82,7 @@ public BoxAPIConnection(String clientID, String clientSecret, String accessToken this.tokenURL = TOKEN_URL_STRING; this.baseURL = DEFAULT_BASE_URL; this.baseUploadURL = DEFAULT_BASE_UPLOAD_URL; + this.baseUploadSessionURL = DEFAULT_BASE_UPLOAD_SESSION_URL; this.autoRefresh = true; this.maxRequestAttempts = DEFAULT_MAX_ATTEMPTS; this.refreshLock = new ReentrantReadWriteLock(); @@ -237,6 +240,22 @@ public void setBaseUploadURL(String baseUploadURL) { this.baseUploadURL = baseUploadURL; } + /** + * Gets the base upload session URL that's used when performing supercharged uploads to Box. + * @return the base upload session URL. + */ + public String getBaseUploadSessionURL() { + return this.baseUploadSessionURL; + } + + /** + * Sets the base upload URL to be used when performing supercharged uploads to Box. + * @param baseUploadSessionURL a base upload URL. + */ + public void setBaseUploadSessionURL(String baseUploadSessionURL) { + this.baseUploadSessionURL = baseUploadSessionURL; + } + /** * Gets the user agent that's used when sending requests to the Box API. * @return the user agent. diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 730b38ad9..46620e70f 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -6,11 +6,7 @@ import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.EnumSet; -import java.util.List; +import java.util.*; import com.eclipsesource.json.JsonArray; import com.eclipsesource.json.JsonObject; @@ -63,6 +59,11 @@ public enum ThumbnailFileType { private static final URLTemplate GET_TASKS_URL_TEMPLATE = new URLTemplate("files/%s/tasks"); private static final URLTemplate GET_THUMBNAIL_PNG_TEMPLATE = new URLTemplate("files/%s/thumbnail.png"); private static final URLTemplate GET_THUMBNAIL_JPG_TEMPLATE = new URLTemplate("files/%s/thumbnail.jpg"); + private static final URLTemplate UPLOAD_SESSION_URL_TEMPLATE = new URLTemplate("files/%s/upload-session"); + private static final URLTemplate UPLOAD_SESSION_STATUS_URL_TEMPLATE = new URLTemplate( + "files/upload-session/%s/status"); + private static final URLTemplate ABORT_UPLOAD_SESSION_URL_TEMPLATE = new URLTemplate("files/upload-session/%s"); + private static final int BUFFER_SIZE = 8192; @@ -856,6 +857,42 @@ public BoxFile.Info setCollections(BoxCollection... collections) { return new Info(jsonObject); } + public BoxFileUploadSession createUploadSession(long fileSize) { + String queryString = new QueryStringBuilder().appendParam("file_size", fileSize).toString(); + URL url = UPLOAD_SESSION_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseUploadSessionURL(), + queryString, this.getID()); + + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); + request.addHeader("Content-Type", "application/json"); + + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + System.out.println("Response: " + jsonObject); + + return new BoxFileUploadSession(jsonObject); + } + + public BoxFileUploadSession getUploadSessionStatus(String sessionId) { + URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), sessionId); + + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET"); + + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + System.out.println("Response: " + jsonObject); + + return new BoxFileUploadSession(jsonObject); + } + + public void abortUploadSession(String sessionId) { + URL url = ABORT_UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), sessionId); + + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "DELETE"); + + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + } + /** * Contains information about a BoxFile. */ diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java new file mode 100644 index 000000000..0deee7543 --- /dev/null +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -0,0 +1,146 @@ +package com.box.sdk; + +import com.eclipsesource.json.JsonObject; +import com.eclipsesource.json.JsonValue; + +import java.net.MalformedURLException; +import java.net.URL; +import java.text.ParseException; +import java.util.Date; +import java.util.Map; + +/** + * + */ +public class BoxFileUploadSession extends BoxJSONObject { + + private Date sessionExpiresAt; + private String uploadSessionId; + private long partSize; + private Endpoints sessionEndpoints; + private int totalParts; + private int partsProcessed; + + public BoxFileUploadSession() { + super(); + } + + public BoxFileUploadSession(String json) { + super(json); + } + + BoxFileUploadSession(JsonObject jsonObject) { + super(jsonObject); + } + + public Date getSessionExpiresAt() { + return sessionExpiresAt; + } + + public String getUploadSessionId() { + return uploadSessionId; + } + + public long getPartSize() { + return partSize; + } + + public Endpoints getSessionEndpoints() { + return sessionEndpoints; + } + + public int getTotalParts() { + return this.totalParts; + } + + public int getPartsProcessed() { + return this.partsProcessed; + } + + @Override + protected void parseJSONMember(JsonObject.Member member) { + + String memberName = member.getName(); + JsonValue value = member.getValue(); + if (memberName.equals("session_expires_at")) { + try { + String dateStr = value.asString(); + this.sessionExpiresAt = BoxDateFormat.parse(dateStr.substring(0, dateStr.length()-1) + "-00:00"); + } catch (ParseException pe) { + assert false : "A ParseException indicates a bug in the SDK."; + } + } else if (memberName.equals("upload_session_id")) { + this.uploadSessionId = value.asString(); + } else if (memberName.equals("part_size")) { + this.partSize = Double.valueOf(value.toString()).longValue(); + } else if (memberName.equals("session_endpoints")) { + this.sessionEndpoints = new Endpoints(value.asObject()); + } else if (memberName.equals("total_parts")) { + this.totalParts = value.asInt(); + } if (memberName.equals("num_parts_processed")) { + this.partsProcessed = value.asInt(); + } + } + + public class Endpoints extends BoxJSONObject { + private URL listPartsEndpoint; + private URL commitEndpoint; + private URL uploadPartEndpoint; + private URL statusEndpoint; + private URL abortEndpoint; + + public Endpoints() { + super(); + } + + public Endpoints(String json) { + super(json); + } + + Endpoints(JsonObject jsonObject) { + super(jsonObject); + } + + public URL getListPartsEndpoint() { + return listPartsEndpoint; + } + + public URL getCommitEndpoint() { + return commitEndpoint; + } + + public URL getUploadPartEndpoint() { + return uploadPartEndpoint; + } + + public URL getStatusEndpoint() { + return statusEndpoint; + } + + public URL getAbortEndpoint() { + return abortEndpoint; + } + + @Override + protected void parseJSONMember(JsonObject.Member member) { + + String memberName = member.getName(); + JsonValue value = member.getValue(); + try { + if (memberName.equals("list_parts")) { + this.listPartsEndpoint = new URL(value.asString()); + } else if (memberName.equals("commit")) { + this.commitEndpoint = new URL(value.asString()); + } else if (memberName.equals("upload_part")) { + this.uploadPartEndpoint = new URL(value.asString()); + } else if (memberName.equals("status")) { + this.statusEndpoint = new URL(value.asString()); + } else if (memberName.equals("abort")) { + this.abortEndpoint = new URL(value.asString()); + } + } catch(MalformedURLException mue) { + assert false : "A ParseException indicates a bug in the SDK."; + } + } + } +} diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index d07ee7039..13d6f0cc1 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -42,6 +42,7 @@ public class BoxFolder extends BoxItem implements Iterable { private static final URLTemplate GET_ITEMS_URL = new URLTemplate("folders/%s/items/"); private static final URLTemplate SEARCH_URL_TEMPLATE = new URLTemplate("search"); private static final URLTemplate METADATA_URL_TEMPLATE = new URLTemplate("folders/%s/metadata/%s/%s"); + private static final URLTemplate UPLOAD_SESSION_URL_TEMPLATE = new URLTemplate("files/upload-session"); /** * Constructs a BoxFolder for a folder with a given ID. @@ -728,6 +729,24 @@ public void deleteMetadata(String templateName, String scope) { response.disconnect(); } + public BoxFileUploadSession createUploadSession(String folderId, long fileSize, String fileName) { + + String queryString = new QueryStringBuilder() + .appendParam("folder_id", folderId) + .appendParam("file_size", fileSize) + .appendParam("file_name", fileName) + .toString(); + + URL url = UPLOAD_SESSION_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseUploadSessionURL(), + queryString); + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + System.out.println("Response: " + jsonObject); + + return new BoxFileUploadSession(jsonObject); + } + /** * Contains information about a BoxFolder. */ diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index ca1a1ace3..2a87411e6 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -966,6 +966,54 @@ public void setCollectionsWithInfoSucceeds() { uploadedFile.delete(); } + @Test + @Category(IntegrationTest.class) + public void uploadSessionSucceeds() { + BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); + BoxFolder rootFolder = BoxFolder.getRootFolder(api); + String fileName = "[createUploadSessionSucceeds] Test File.txt"; + String fileContent = "Test file"; + byte[] fileBytes = fileContent.getBytes(StandardCharsets.UTF_8); + + InputStream uploadStream = new ByteArrayInputStream(fileBytes); + BoxFile uploadedFile = rootFolder.uploadFile(uploadStream, fileName).getResource(); + try { + BoxFileUploadSession session = uploadedFile.createUploadSession(10000000); + Assert.assertNotNull(session.getUploadSessionId()); + Assert.assertNotNull(session.getSessionExpiresAt()); + Assert.assertNotNull(session.getPartSize()); + + BoxFileUploadSession.Endpoints endpoints = session.getSessionEndpoints(); + Assert.assertNotNull(endpoints); + Assert.assertNotNull(endpoints.getUploadPartEndpoint()); + Assert.assertNotNull(endpoints.getStatusEndpoint()); + Assert.assertNotNull(endpoints.getListPartsEndpoint()); + Assert.assertNotNull(endpoints.getCommitEndpoint()); + Assert.assertNotNull(endpoints.getAbortEndpoint()); + + //Verify the status of the session + getUploadSessionStatusSucceeds(uploadedFile, session.getUploadSessionId()); + + //Verify the delete session + abortUploadSessionStatusSucceeds(uploadedFile, session.getUploadSessionId()); + } finally { + uploadedFile.delete(); + } + } + + private void getUploadSessionStatusSucceeds(BoxFile file, String sessionId) { + BoxFileUploadSession session = file.getUploadSessionStatus(sessionId); + Assert.assertNotNull(session.getSessionExpiresAt()); + Assert.assertNotNull(session.getPartSize()); + Assert.assertNotNull(session.getTotalParts()); + Assert.assertNotNull(session.getPartsProcessed()); + } + + private void abortUploadSessionStatusSucceeds(BoxFile file, String sessionId) { + file.abortUploadSession(sessionId); + + } + private static byte[] readAllBytes(String fileName) throws IOException { RandomAccessFile f = new RandomAccessFile(fileName, "r"); byte[] b = new byte[(int) f.length()]; diff --git a/src/test/java/com/box/sdk/BoxFolderTest.java b/src/test/java/com/box/sdk/BoxFolderTest.java index fdc7d75d3..c24f22642 100644 --- a/src/test/java/com/box/sdk/BoxFolderTest.java +++ b/src/test/java/com/box/sdk/BoxFolderTest.java @@ -1065,4 +1065,23 @@ public void sharedLinkInfoHasEffectiveAccess() { folder.delete(true); } + + @Test + @Category(IntegrationTest.class) + public void createUploadSessionSucceeds() { + BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); + BoxFolder rootFolder = BoxFolder.getRootFolder(api); + BoxFileUploadSession session = rootFolder.createUploadSession("0", 1000000, "Test_File.txt"); + Assert.assertNotNull(session.getUploadSessionId()); + Assert.assertNotNull(session.getSessionExpiresAt()); + Assert.assertNotNull(session.getPartSize()); + + BoxFileUploadSession.Endpoints endpoints = session.getSessionEndpoints(); + Assert.assertNotNull(endpoints); + Assert.assertNotNull(endpoints.getUploadPartEndpoint()); + Assert.assertNotNull(endpoints.getStatusEndpoint()); + Assert.assertNotNull(endpoints.getListPartsEndpoint()); + Assert.assertNotNull(endpoints.getCommitEndpoint()); + Assert.assertNotNull(endpoints.getAbortEndpoint()); + } } From 43c935bc0c024fd590d9b1bb3239510e400a0c85 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Thu, 23 Feb 2017 11:14:29 -0800 Subject: [PATCH 087/119] Abort session implementation --- src/main/java/com/box/sdk/BoxFile.java | 6 +++--- src/test/java/com/box/sdk/BoxFileTest.java | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 46620e70f..835b52414 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -873,7 +873,7 @@ public BoxFileUploadSession createUploadSession(long fileSize) { } public BoxFileUploadSession getUploadSessionStatus(String sessionId) { - URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), sessionId); + URL url = UPLOAD_SESSION_STATUS_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), sessionId); BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET"); @@ -889,8 +889,8 @@ public void abortUploadSession(String sessionId) { BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "DELETE"); - BoxJSONResponse response = (BoxJSONResponse) request.send(); - JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + BoxAPIResponse response = (BoxAPIResponse) request.send(); + System.out.println("Abort session status: " + response.getResponseCode()); } /** diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index 2a87411e6..19ad7a168 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -1012,6 +1012,14 @@ private void getUploadSessionStatusSucceeds(BoxFile file, String sessionId) { private void abortUploadSessionStatusSucceeds(BoxFile file, String sessionId) { file.abortUploadSession(sessionId); + try { + BoxFileUploadSession session = file.getUploadSessionStatus(sessionId); + + //If the session is aborted, this line should not be executed. + Assert.assertFalse("Upload session is not deleted", true); + } catch(BoxAPIException apiEx) { + Assert.assertEquals(apiEx.getResponseCode(), 404); + } } private static byte[] readAllBytes(String fileName) throws IOException { From 98bc132f445b4a26a2918ecf42da823562f1e655 Mon Sep 17 00:00:00 2001 From: Harish Gokavarapu Date: Thu, 23 Feb 2017 17:32:48 -0800 Subject: [PATCH 088/119] First attempt at generalising FileUploadSession --- src/main/java/com/box/sdk/BoxAPIRequest.java | 6 + src/main/java/com/box/sdk/BoxFile.java | 23 +--- .../com/box/sdk/BoxFileUploadSession.java | 108 +++++++++++++----- src/main/java/com/box/sdk/BoxFolder.java | 2 +- .../java/com/box/sdk/http/ContentType.java | 8 ++ .../java/com/box/sdk/http/HttpHeaders.java | 12 ++ .../java/com/box/sdk/http/HttpMethod.java | 8 ++ 7 files changed, 113 insertions(+), 54 deletions(-) create mode 100644 src/main/java/com/box/sdk/http/ContentType.java create mode 100644 src/main/java/com/box/sdk/http/HttpHeaders.java create mode 100644 src/main/java/com/box/sdk/http/HttpMethod.java diff --git a/src/main/java/com/box/sdk/BoxAPIRequest.java b/src/main/java/com/box/sdk/BoxAPIRequest.java index 11e779cfa..5e15bd372 100644 --- a/src/main/java/com/box/sdk/BoxAPIRequest.java +++ b/src/main/java/com/box/sdk/BoxAPIRequest.java @@ -1,5 +1,7 @@ package com.box.sdk; +import com.box.sdk.http.HttpMethod; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -76,6 +78,10 @@ public BoxAPIRequest(BoxAPIConnection api, URL url, String method) { this.addHeader("Accept-Charset", "utf-8"); } + public BoxAPIRequest(BoxAPIConnection api, URL uploadPartEndpoint, HttpMethod post) { + this(api, uploadPartEndpoint, post.name()); + } + /** * Adds an HTTP header to this request. * @param key the header key. diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 835b52414..0eda0637c 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -869,28 +869,7 @@ public BoxFileUploadSession createUploadSession(long fileSize) { JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); System.out.println("Response: " + jsonObject); - return new BoxFileUploadSession(jsonObject); - } - - public BoxFileUploadSession getUploadSessionStatus(String sessionId) { - URL url = UPLOAD_SESSION_STATUS_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), sessionId); - - BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET"); - - BoxJSONResponse response = (BoxJSONResponse) request.send(); - JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); - System.out.println("Response: " + jsonObject); - - return new BoxFileUploadSession(jsonObject); - } - - public void abortUploadSession(String sessionId) { - URL url = ABORT_UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), sessionId); - - BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "DELETE"); - - BoxAPIResponse response = (BoxAPIResponse) request.send(); - System.out.println("Abort session status: " + response.getResponseCode()); + return new BoxFileUploadSession(this.getAPI(), jsonObject); } /** diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index 0deee7543..fc8032f71 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -1,36 +1,51 @@ package com.box.sdk; +import com.box.sdk.http.ContentType; +import com.box.sdk.http.HttpHeaders; +import com.box.sdk.http.HttpMethod; import com.eclipsesource.json.JsonObject; import com.eclipsesource.json.JsonValue; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.text.ParseException; import java.util.Date; -import java.util.Map; /** * */ public class BoxFileUploadSession extends BoxJSONObject { + private BoxAPIConnection api; private Date sessionExpiresAt; private String uploadSessionId; - private long partSize; private Endpoints sessionEndpoints; + private long partSize; private int totalParts; private int partsProcessed; - public BoxFileUploadSession() { - super(); + private BoxAPIConnection getAPI() { + return api; } - public BoxFileUploadSession(String json) { - super(json); + private void setAPI(BoxAPIConnection api) { + this.api = api; } - BoxFileUploadSession(JsonObject jsonObject) { - super(jsonObject); + public BoxFileUploadSession getResource() { + return BoxFileUploadSession.this; + } + + public int getTotalParts() { + return this.totalParts; + } + + public int getPartsProcessed() { + return this.partsProcessed; } public Date getSessionExpiresAt() { @@ -41,31 +56,27 @@ public String getUploadSessionId() { return uploadSessionId; } - public long getPartSize() { - return partSize; - } - public Endpoints getSessionEndpoints() { return sessionEndpoints; } - public int getTotalParts() { - return this.totalParts; + public long getPartSize() { + return partSize; } - public int getPartsProcessed() { - return this.partsProcessed; + public BoxFileUploadSession(BoxAPIConnection api, JsonObject jsonObject) { + super(jsonObject); + this.api = api; } - @Override protected void parseJSONMember(JsonObject.Member member) { String memberName = member.getName(); JsonValue value = member.getValue(); if (memberName.equals("session_expires_at")) { - try { + try { String dateStr = value.asString(); - this.sessionExpiresAt = BoxDateFormat.parse(dateStr.substring(0, dateStr.length()-1) + "-00:00"); + this.sessionExpiresAt = BoxDateFormat.parse(dateStr.substring(0, dateStr.length()-1) + "-00:00"); } catch (ParseException pe) { assert false : "A ParseException indicates a bug in the SDK."; } @@ -83,23 +94,23 @@ protected void parseJSONMember(JsonObject.Member member) { } public class Endpoints extends BoxJSONObject { - private URL listPartsEndpoint; - private URL commitEndpoint; - private URL uploadPartEndpoint; - private URL statusEndpoint; - private URL abortEndpoint; + private URL listPartsEndpoint; + private URL commitEndpoint; + private URL uploadPartEndpoint; + private URL statusEndpoint; + private URL abortEndpoint; public Endpoints() { - super(); - } + super(); + } - public Endpoints(String json) { + public Endpoints(String json) { super(json); - } + } - Endpoints(JsonObject jsonObject) { + Endpoints(JsonObject jsonObject) { super(jsonObject); - } + } public URL getListPartsEndpoint() { return listPartsEndpoint; @@ -128,7 +139,7 @@ protected void parseJSONMember(JsonObject.Member member) { JsonValue value = member.getValue(); try { if (memberName.equals("list_parts")) { - this.listPartsEndpoint = new URL(value.asString()); + this.listPartsEndpoint = new URL(value.asString()); } else if (memberName.equals("commit")) { this.commitEndpoint = new URL(value.asString()); } else if (memberName.equals("upload_part")) { @@ -143,4 +154,39 @@ protected void parseJSONMember(JsonObject.Member member) { } } } + + /** + * + */ + public void uploadPart(String xBoxPartId, byte[] bytes, long offset, Long partSize, long totalSizeOfFile, String contentRangeUnits) throws MalformedURLException, NoSuchAlgorithmException { + BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), this.getSessionEndpoints().getUploadPartEndpoint(), HttpMethod.POST); + request.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_OCTET_STREAM); + request.addHeader(HttpHeaders.X_BOX_PART_ID, xBoxPartId); + byte[] digestBytes = MessageDigest.getInstance("SHA1").digest(bytes); + String digest = Base64.encode(digestBytes); + request.addHeader("Digest", "sha=" + digest); + if(null == contentRangeUnits){ + contentRangeUnits = "bytes"; + } + request.addHeader(HttpHeaders.CONTENT_RANGE, contentRangeUnits+" "+offset+"-"+(offset+partSize-1)+"/"+totalSizeOfFile); + request.addHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(bytes.length)); + request.setBody(new ByteArrayInputStream(bytes)); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + } + + public BoxFileUploadSession getUploadSessionStatus(String sessionId) { + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), this.getSessionEndpoints().getStatusEndpoint(), "GET"); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + System.out.println("Response: " + jsonObject); + + return new BoxFileUploadSession(this.getAPI(), jsonObject); + } + + + public void abortUploadSession(String sessionId) { + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), this.getSessionEndpoints().getAbortEndpoint(), "DELETE"); + BoxAPIResponse response = (BoxAPIResponse) request.send(); + System.out.println("Abort session status: " + response.getResponseCode()); + } } diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index 13d6f0cc1..c6df62074 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -744,7 +744,7 @@ public BoxFileUploadSession createUploadSession(String folderId, long fileSize, JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); System.out.println("Response: " + jsonObject); - return new BoxFileUploadSession(jsonObject); + return new BoxFileUploadSession(this.getAPI(), jsonObject); } /** diff --git a/src/main/java/com/box/sdk/http/ContentType.java b/src/main/java/com/box/sdk/http/ContentType.java new file mode 100644 index 000000000..4f6b4ebb4 --- /dev/null +++ b/src/main/java/com/box/sdk/http/ContentType.java @@ -0,0 +1,8 @@ +package com.box.sdk.http; + +/** + * + */ +public class ContentType { + public static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; +} diff --git a/src/main/java/com/box/sdk/http/HttpHeaders.java b/src/main/java/com/box/sdk/http/HttpHeaders.java new file mode 100644 index 000000000..fa9b12404 --- /dev/null +++ b/src/main/java/com/box/sdk/http/HttpHeaders.java @@ -0,0 +1,12 @@ +package com.box.sdk.http; + +/** + * + */ +public class HttpHeaders { + public static final String CONTENT_TYPE = "Content-Type"; + public static final String CONTENT_LENGTH = "Content-Length"; + public static final String CONTENT_RANGE = "Content-Range"; + public static final String DIGEST = "Digest"; + public static final String X_BOX_PART_ID = "X-Box-Part-Id"; +} diff --git a/src/main/java/com/box/sdk/http/HttpMethod.java b/src/main/java/com/box/sdk/http/HttpMethod.java new file mode 100644 index 000000000..4121191b5 --- /dev/null +++ b/src/main/java/com/box/sdk/http/HttpMethod.java @@ -0,0 +1,8 @@ +package com.box.sdk.http; + +/** + * + */ +public enum HttpMethod { + GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; +} From f6a4d1a696785f6b880db6bfd26f45023d61dbcc Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Sun, 26 Feb 2017 19:20:42 -0800 Subject: [PATCH 089/119] Upload implementation changes & code refactoring to match existing code style --- src/main/java/com/box/sdk/BoxAPIRequest.java | 8 +- src/main/java/com/box/sdk/BoxFile.java | 12 +- .../com/box/sdk/BoxFileUploadSession.java | 436 +++++++++++------- src/main/java/com/box/sdk/BoxFolder.java | 7 +- src/main/java/com/box/sdk/BoxJSONRequest.java | 14 + src/main/java/com/box/sdk/BoxResource.java | 2 + .../java/com/box/sdk/http/ContentType.java | 10 +- .../java/com/box/sdk/http/HttpHeaders.java | 17 +- .../java/com/box/sdk/http/HttpMethod.java | 3 +- 9 files changed, 314 insertions(+), 195 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxAPIRequest.java b/src/main/java/com/box/sdk/BoxAPIRequest.java index 5e15bd372..de7b3585a 100644 --- a/src/main/java/com/box/sdk/BoxAPIRequest.java +++ b/src/main/java/com/box/sdk/BoxAPIRequest.java @@ -1,7 +1,5 @@ package com.box.sdk; -import com.box.sdk.http.HttpMethod; - import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -16,6 +14,8 @@ import java.util.logging.Level; import java.util.logging.Logger; +import com.box.sdk.http.HttpMethod; + /** * Used to make HTTP requests to the Box API. * @@ -78,8 +78,8 @@ public BoxAPIRequest(BoxAPIConnection api, URL url, String method) { this.addHeader("Accept-Charset", "utf-8"); } - public BoxAPIRequest(BoxAPIConnection api, URL uploadPartEndpoint, HttpMethod post) { - this(api, uploadPartEndpoint, post.name()); + public BoxAPIRequest(BoxAPIConnection api, URL uploadPartEndpoint, HttpMethod method) { + this(api, uploadPartEndpoint, method.name()); } /** diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 0eda0637c..bda7859e5 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -6,7 +6,11 @@ import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.EnumSet; +import java.util.List; import com.eclipsesource.json.JsonArray; import com.eclipsesource.json.JsonObject; @@ -857,7 +861,7 @@ public BoxFile.Info setCollections(BoxCollection... collections) { return new Info(jsonObject); } - public BoxFileUploadSession createUploadSession(long fileSize) { + public BoxFileUploadSession.Info createUploadSession(long fileSize) { String queryString = new QueryStringBuilder().appendParam("file_size", fileSize).toString(); URL url = UPLOAD_SESSION_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseUploadSessionURL(), queryString, this.getID()); @@ -869,7 +873,9 @@ public BoxFileUploadSession createUploadSession(long fileSize) { JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); System.out.println("Response: " + jsonObject); - return new BoxFileUploadSession(this.getAPI(), jsonObject); + String sessionId = jsonObject.get("upload_session_id").asString(); + BoxFileUploadSession session = new BoxFileUploadSession(this.getAPI(), sessionId); + return session.new Info(jsonObject); } /** diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index fc8032f71..7cae4c53f 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -1,11 +1,5 @@ package com.box.sdk; -import com.box.sdk.http.ContentType; -import com.box.sdk.http.HttpHeaders; -import com.box.sdk.http.HttpMethod; -import com.eclipsesource.json.JsonObject; -import com.eclipsesource.json.JsonValue; - import java.io.ByteArrayInputStream; import java.io.InputStream; import java.net.MalformedURLException; @@ -15,178 +9,266 @@ import java.text.ParseException; import java.util.Date; +import com.box.sdk.http.ContentType; +import com.box.sdk.http.HttpHeaders; +import com.box.sdk.http.HttpMethod; +import com.eclipsesource.json.JsonObject; +import com.eclipsesource.json.JsonValue; + /** * */ -public class BoxFileUploadSession extends BoxJSONObject { - - private BoxAPIConnection api; - private Date sessionExpiresAt; - private String uploadSessionId; - private Endpoints sessionEndpoints; - private long partSize; - private int totalParts; - private int partsProcessed; - - private BoxAPIConnection getAPI() { - return api; - } - - private void setAPI(BoxAPIConnection api) { - this.api = api; - } - - public BoxFileUploadSession getResource() { - return BoxFileUploadSession.this; - } - - public int getTotalParts() { - return this.totalParts; - } - - public int getPartsProcessed() { - return this.partsProcessed; - } - - public Date getSessionExpiresAt() { - return sessionExpiresAt; - } - - public String getUploadSessionId() { - return uploadSessionId; - } - - public Endpoints getSessionEndpoints() { - return sessionEndpoints; - } - - public long getPartSize() { - return partSize; - } - - public BoxFileUploadSession(BoxAPIConnection api, JsonObject jsonObject) { - super(jsonObject); - this.api = api; - } - - protected void parseJSONMember(JsonObject.Member member) { - - String memberName = member.getName(); - JsonValue value = member.getValue(); - if (memberName.equals("session_expires_at")) { - try { - String dateStr = value.asString(); - this.sessionExpiresAt = BoxDateFormat.parse(dateStr.substring(0, dateStr.length()-1) + "-00:00"); - } catch (ParseException pe) { - assert false : "A ParseException indicates a bug in the SDK."; - } - } else if (memberName.equals("upload_session_id")) { - this.uploadSessionId = value.asString(); - } else if (memberName.equals("part_size")) { - this.partSize = Double.valueOf(value.toString()).longValue(); - } else if (memberName.equals("session_endpoints")) { - this.sessionEndpoints = new Endpoints(value.asObject()); - } else if (memberName.equals("total_parts")) { - this.totalParts = value.asInt(); - } if (memberName.equals("num_parts_processed")) { - this.partsProcessed = value.asInt(); - } - } - - public class Endpoints extends BoxJSONObject { - private URL listPartsEndpoint; - private URL commitEndpoint; - private URL uploadPartEndpoint; - private URL statusEndpoint; - private URL abortEndpoint; - - public Endpoints() { - super(); - } - - public Endpoints(String json) { - super(json); - } - - Endpoints(JsonObject jsonObject) { - super(jsonObject); - } - - public URL getListPartsEndpoint() { - return listPartsEndpoint; - } - - public URL getCommitEndpoint() { - return commitEndpoint; - } - - public URL getUploadPartEndpoint() { - return uploadPartEndpoint; - } - - public URL getStatusEndpoint() { - return statusEndpoint; - } - - public URL getAbortEndpoint() { - return abortEndpoint; - } - - @Override - protected void parseJSONMember(JsonObject.Member member) { - - String memberName = member.getName(); - JsonValue value = member.getValue(); - try { - if (memberName.equals("list_parts")) { - this.listPartsEndpoint = new URL(value.asString()); - } else if (memberName.equals("commit")) { - this.commitEndpoint = new URL(value.asString()); - } else if (memberName.equals("upload_part")) { - this.uploadPartEndpoint = new URL(value.asString()); - } else if (memberName.equals("status")) { - this.statusEndpoint = new URL(value.asString()); - } else if (memberName.equals("abort")) { - this.abortEndpoint = new URL(value.asString()); - } - } catch(MalformedURLException mue) { - assert false : "A ParseException indicates a bug in the SDK."; - } - } - } - - /** - * - */ - public void uploadPart(String xBoxPartId, byte[] bytes, long offset, Long partSize, long totalSizeOfFile, String contentRangeUnits) throws MalformedURLException, NoSuchAlgorithmException { - BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), this.getSessionEndpoints().getUploadPartEndpoint(), HttpMethod.POST); - request.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_OCTET_STREAM); - request.addHeader(HttpHeaders.X_BOX_PART_ID, xBoxPartId); - byte[] digestBytes = MessageDigest.getInstance("SHA1").digest(bytes); - String digest = Base64.encode(digestBytes); - request.addHeader("Digest", "sha=" + digest); - if(null == contentRangeUnits){ - contentRangeUnits = "bytes"; - } - request.addHeader(HttpHeaders.CONTENT_RANGE, contentRangeUnits+" "+offset+"-"+(offset+partSize-1)+"/"+totalSizeOfFile); - request.addHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(bytes.length)); - request.setBody(new ByteArrayInputStream(bytes)); - BoxJSONResponse response = (BoxJSONResponse) request.send(); - } - - public BoxFileUploadSession getUploadSessionStatus(String sessionId) { - BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), this.getSessionEndpoints().getStatusEndpoint(), "GET"); - BoxJSONResponse response = (BoxJSONResponse) request.send(); - JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); - System.out.println("Response: " + jsonObject); - - return new BoxFileUploadSession(this.getAPI(), jsonObject); - } - - - public void abortUploadSession(String sessionId) { - BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), this.getSessionEndpoints().getAbortEndpoint(), "DELETE"); - BoxAPIResponse response = (BoxAPIResponse) request.send(); - System.out.println("Abort session status: " + response.getResponseCode()); - } +public class BoxFileUploadSession extends BoxResource { + + private static final String DIGEST_HEADER_PREFIX_SHA = "sha="; + private static final String DIGEST_ALGORITHM_SHA1 = "SHA1"; + + private static final String MARKER_QUERY_STRING = "marker"; + private static final String LIMIT_QUERY_STRING = "limit"; + + private Info sessionInfo; + + BoxFileUploadSession(BoxAPIConnection api, String id) { + super(api, id); + } + + public class Info extends BoxResource.Info { + private BoxAPIConnection api; + private Date sessionExpiresAt; + private String uploadSessionId; + private Endpoints sessionEndpoints; + private long partSize; + private int totalParts; + private int partsProcessed; + + /** + * Constructs an empty Info object. + */ + public Info() { + super(); + BoxFileUploadSession.this.sessionInfo = this; + } + + /** + * Constructs an Info object by parsing information from a JSON string. + * @param json the JSON string to parse. + */ + public Info(String json) { + super(json); + BoxFileUploadSession.this.sessionInfo = this; + } + + /** + * Constructs an Info object using an already parsed JSON object. + * @param jsonObject the parsed JSON object. + */ + Info(JsonObject jsonObject) { + super(jsonObject); + BoxFileUploadSession.this.sessionInfo = this; + } + + private BoxAPIConnection getAPI() { + return this.api; + } + + private void setAPI(BoxAPIConnection api) { + this.api = api; + } + + public BoxFileUploadSession getResource() { + return BoxFileUploadSession.this; + } + + public int getTotalParts() { + return this.totalParts; + } + + public int getPartsProcessed() { + return this.partsProcessed; + } + + public Date getSessionExpiresAt() { + return this.sessionExpiresAt; + } + + public String getUploadSessionId() { + return this.uploadSessionId; + } + + public Endpoints getSessionEndpoints() { + return this.sessionEndpoints; + } + + public long getPartSize() { + return this.partSize; + } + + protected void parseJSONMember(JsonObject.Member member) { + + String memberName = member.getName(); + JsonValue value = member.getValue(); + if (memberName.equals("session_expires_at")) { + try { + String dateStr = value.asString(); + this.sessionExpiresAt = BoxDateFormat.parse(dateStr.substring(0, dateStr.length() - 1) + "-00:00"); + } catch (ParseException pe) { + assert false : "A ParseException indicates a bug in the SDK."; + } + } else if (memberName.equals("upload_session_id")) { + this.uploadSessionId = value.asString(); + } else if (memberName.equals("part_size")) { + this.partSize = Double.valueOf(value.toString()).longValue(); + } else if (memberName.equals("session_endpoints")) { + this.sessionEndpoints = new Endpoints(value.asObject()); + } else if (memberName.equals("total_parts")) { + this.totalParts = value.asInt(); + } else if (memberName.equals("num_parts_processed")) { + this.partsProcessed = value.asInt(); + } + } + } + + public class Endpoints extends BoxJSONObject { + private URL listPartsEndpoint; + private URL commitEndpoint; + private URL uploadPartEndpoint; + private URL statusEndpoint; + private URL abortEndpoint; + + public Endpoints() { + super(); + } + + public Endpoints(String json) { + super(json); + } + + Endpoints(JsonObject jsonObject) { + super(jsonObject); + } + + public URL getListPartsEndpoint() { + return this.listPartsEndpoint; + } + + public URL getCommitEndpoint() { + return this.commitEndpoint; + } + + public URL getUploadPartEndpoint() { + return this.uploadPartEndpoint; + } + + public URL getStatusEndpoint() { + return this.statusEndpoint; + } + + public URL getAbortEndpoint() { + return this.abortEndpoint; + } + + @Override + protected void parseJSONMember(JsonObject.Member member) { + + String memberName = member.getName(); + JsonValue value = member.getValue(); + try { + if (memberName.equals("list_parts")) { + this.listPartsEndpoint = new URL(value.asString()); + } else if (memberName.equals("commit")) { + this.commitEndpoint = new URL(value.asString()); + } else if (memberName.equals("upload_part")) { + this.uploadPartEndpoint = new URL(value.asString()); + } else if (memberName.equals("status")) { + this.statusEndpoint = new URL(value.asString()); + } else if (memberName.equals("abort")) { + this.abortEndpoint = new URL(value.asString()); + } + } catch (MalformedURLException mue) { + assert false : "A ParseException indicates a bug in the SDK."; + } + } + } + + public void uploadPart(String partId, byte[] bytes, long startRange, Long partSize, long totalSizeOfFile) + throws MalformedURLException, NoSuchAlgorithmException { + + URL uploadPartURL = this.sessionInfo.getSessionEndpoints().getUploadPartEndpoint(); + BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), uploadPartURL, HttpMethod.POST); + request.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_OCTET_STREAM); + request.addHeader(HttpHeaders.X_BOX_PART_ID, partId); + + byte[] digestBytes = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1).digest(bytes); + String digest = Base64.encode(digestBytes); + request.addHeader(HttpHeaders.DIGEST, DIGEST_HEADER_PREFIX_SHA + digest); + request.addHeader(HttpHeaders.CONTENT_RANGE, + "bytes " + startRange + "-" + (startRange + partSize - 1) + "/" + totalSizeOfFile); + request.addHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(bytes.length)); + + request.setBody(new ByteArrayInputStream(bytes)); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + } + + public int listParts(String sessionId, int marker, int limit) { + URL listPartsURL = this.sessionInfo.getSessionEndpoints().getListPartsEndpoint(); + URLTemplate template = new URLTemplate(listPartsURL.toString()); + + String queryString = new QueryStringBuilder() + .appendParam(MARKER_QUERY_STRING, marker) + .appendParam(LIMIT_QUERY_STRING, limit) + .toString(); + URL url = template.buildWithQuery("", queryString); + + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, HttpMethod.GET); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); +// System.out.println("Response: " + jsonObject); + + return response.getResponseCode(); + } + + public int commit(String sessionId, String digest, JsonObject jsonObject, String ifMatch, String ifNonMatch) { + URL commitURL = this.sessionInfo.getSessionEndpoints().getCommitEndpoint(); + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), commitURL, HttpMethod.POST); + request.addHeader(HttpHeaders.DIGEST, DIGEST_HEADER_PREFIX_SHA + digest); + request.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON); + + request.setBody(jsonObject.toString()); + + BoxAPIResponse response = request.send(); + //System.out.println("Response: " + response.getResponseCode()); + + try { + InputStream stream = response.getBody(); + int value = stream.read(); + StringBuffer buffer = new StringBuffer(); + while (value != -1) { + buffer.append(value); + value = stream.read(); + } + //System.out.println("Response: " + buffer.toString()); + } catch (Exception ex) { + ex.printStackTrace(); + } + + return response.getResponseCode(); + } + + public BoxFileUploadSession.Info getUploadSessionStatus() { + URL statusURL = this.sessionInfo.getSessionEndpoints().getStatusEndpoint(); + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), statusURL, HttpMethod.GET); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + + this.sessionInfo.update(jsonObject); + + return this.sessionInfo; + } + + public void abortUploadSession() { + URL abortURL = this.sessionInfo.getSessionEndpoints().getAbortEndpoint(); + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), abortURL, "DELETE"); + BoxAPIResponse response = request.send(); + //System.out.println("Abort session status: " + response.getResponseCode()); + } } diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index c6df62074..c2d933c78 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -729,7 +729,7 @@ public void deleteMetadata(String templateName, String scope) { response.disconnect(); } - public BoxFileUploadSession createUploadSession(String folderId, long fileSize, String fileName) { + public BoxFileUploadSession.Info createUploadSession(String folderId, long fileSize, String fileName) { String queryString = new QueryStringBuilder() .appendParam("folder_id", folderId) @@ -744,7 +744,10 @@ public BoxFileUploadSession createUploadSession(String folderId, long fileSize, JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); System.out.println("Response: " + jsonObject); - return new BoxFileUploadSession(this.getAPI(), jsonObject); + String sessionId = jsonObject.get("upload_session_id").asString(); + BoxFileUploadSession session = new BoxFileUploadSession(this.getAPI(), sessionId); + + return session.new Info(jsonObject); } /** diff --git a/src/main/java/com/box/sdk/BoxJSONRequest.java b/src/main/java/com/box/sdk/BoxJSONRequest.java index c67e51a40..1c86b1241 100644 --- a/src/main/java/com/box/sdk/BoxJSONRequest.java +++ b/src/main/java/com/box/sdk/BoxJSONRequest.java @@ -2,6 +2,8 @@ import java.net.URL; +import com.box.sdk.http.HttpMethod; + /** * Used to make HTTP requests containing JSON to the Box API. * @@ -23,6 +25,18 @@ public BoxJSONRequest(BoxAPIConnection api, URL url, String method) { this.addHeader("Content-Type", "application/json"); } + /** + * Constructs an authenticated BoxJSONRequest using a provided BoxAPIConnection. + * @param api an API connection for authenticating the request. + * @param url the URL of the request. + * @param method the HTTP method of the request. + */ + public BoxJSONRequest(BoxAPIConnection api, URL url, HttpMethod method) { + super(api, url, method); + + this.addHeader("Content-Type", "application/json"); + } + /** * Sets the body of this request to a given JSON string. * @param body the JSON string to use as the body. diff --git a/src/main/java/com/box/sdk/BoxResource.java b/src/main/java/com/box/sdk/BoxResource.java index 1dc4299bb..4deb7343e 100644 --- a/src/main/java/com/box/sdk/BoxResource.java +++ b/src/main/java/com/box/sdk/BoxResource.java @@ -60,6 +60,8 @@ private static Map> initResourceClassByType result.put(getResourceType(BoxLegalHoldPolicy.class), BoxLegalHoldPolicy.class); result.put(getResourceType(BoxLegalHoldAssignment.class), BoxLegalHoldAssignment.class); result.put(getResourceType(BoxFileVersionLegalHold.class), BoxFileVersionLegalHold.class); + result.put(getResourceType(BoxFileUploadSession.class), BoxFileUploadSession.class); + return Collections.unmodifiableMap(result); } diff --git a/src/main/java/com/box/sdk/http/ContentType.java b/src/main/java/com/box/sdk/http/ContentType.java index 4f6b4ebb4..c16312e1d 100644 --- a/src/main/java/com/box/sdk/http/ContentType.java +++ b/src/main/java/com/box/sdk/http/ContentType.java @@ -3,6 +3,12 @@ /** * */ -public class ContentType { - public static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; +public final class ContentType { + + public static final String APPLICATION_JSON = "application/json"; + public static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; + + //Prevents instantiation + private ContentType() { + } } diff --git a/src/main/java/com/box/sdk/http/HttpHeaders.java b/src/main/java/com/box/sdk/http/HttpHeaders.java index fa9b12404..ecb942aa1 100644 --- a/src/main/java/com/box/sdk/http/HttpHeaders.java +++ b/src/main/java/com/box/sdk/http/HttpHeaders.java @@ -3,10 +3,15 @@ /** * */ -public class HttpHeaders { - public static final String CONTENT_TYPE = "Content-Type"; - public static final String CONTENT_LENGTH = "Content-Length"; - public static final String CONTENT_RANGE = "Content-Range"; - public static final String DIGEST = "Digest"; - public static final String X_BOX_PART_ID = "X-Box-Part-Id"; +public final class HttpHeaders { + + public static final String CONTENT_LENGTH = "Content-Length"; + public static final String CONTENT_RANGE = "Content-Range"; + public static final String CONTENT_TYPE = "Content-Type"; + public static final String DIGEST = "Digest"; + public static final String X_BOX_PART_ID = "X-Box-Part-Id"; + + //Prevents instantiation + private HttpHeaders() { + } } diff --git a/src/main/java/com/box/sdk/http/HttpMethod.java b/src/main/java/com/box/sdk/http/HttpMethod.java index 4121191b5..062b94f54 100644 --- a/src/main/java/com/box/sdk/http/HttpMethod.java +++ b/src/main/java/com/box/sdk/http/HttpMethod.java @@ -4,5 +4,6 @@ * */ public enum HttpMethod { - GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; + + GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; } From 29940b495faeacff9030f026cfeb8a60f8a0891d Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Sun, 26 Feb 2017 23:05:02 -0800 Subject: [PATCH 090/119] Upload session list parts and commit are added --- .../com/box/sdk/BoxFileUploadSessionPart.java | 73 +++++++++++++++++++ .../box/sdk/BoxFileUploadSessionPartList.java | 57 +++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 src/main/java/com/box/sdk/BoxFileUploadSessionPart.java create mode 100644 src/main/java/com/box/sdk/BoxFileUploadSessionPartList.java diff --git a/src/main/java/com/box/sdk/BoxFileUploadSessionPart.java b/src/main/java/com/box/sdk/BoxFileUploadSessionPart.java new file mode 100644 index 000000000..dad0d21be --- /dev/null +++ b/src/main/java/com/box/sdk/BoxFileUploadSessionPart.java @@ -0,0 +1,73 @@ +package com.box.sdk; + +import com.eclipsesource.json.JsonObject; +import com.eclipsesource.json.JsonValue; + +/** + * + */ +public class BoxFileUploadSessionPart extends BoxJSONObject { + + private String partId; + private long offset; + private long size; + + /** + * Constructs an empty BoxFileUploadSessionPart object. + */ + public BoxFileUploadSessionPart() { + super(); + } + + /** + * Constructs an BoxFileUploadSessionPart object by parsing information from a JSON string. + * @param json the JSON string to parse. + */ + public BoxFileUploadSessionPart(String json) { + super(json); + } + + /** + * Constructs an BoxFileUploadSessionPart object using an already parsed JSON object. + * @param jsonObject the parsed JSON object. + */ + BoxFileUploadSessionPart(JsonObject jsonObject) { + super(jsonObject); + } + + public String getPartId() { + return this.partId; + } + + public long getOffset() { + return this.offset; + } + + public long getSize() { + return this.size; + } + + public void setPartId(String partId) { + this.partId = partId; + } + + public void setOffset(long offset) { + this.offset = offset; + } + + public void setSize(long size) { + this.size = size; + } + + protected void parseJSONMember(JsonObject.Member member) { + String memberName = member.getName(); + JsonValue value = member.getValue(); + if (memberName.equals("part_id")) { + this.partId = value.asString(); + } else if (memberName.equals("offset")) { + this.offset = Double.valueOf(value.toString()).longValue(); + } else if (memberName.equals("size")) { + this.size = Double.valueOf(value.toString()).longValue(); + } + } +} diff --git a/src/main/java/com/box/sdk/BoxFileUploadSessionPartList.java b/src/main/java/com/box/sdk/BoxFileUploadSessionPartList.java new file mode 100644 index 000000000..76ca989a1 --- /dev/null +++ b/src/main/java/com/box/sdk/BoxFileUploadSessionPartList.java @@ -0,0 +1,57 @@ +package com.box.sdk; + +import java.util.ArrayList; +import java.util.List; + +import com.eclipsesource.json.JsonArray; +import com.eclipsesource.json.JsonObject; +import com.eclipsesource.json.JsonValue; + +/** + * + */ +public class BoxFileUploadSessionPartList extends BoxJSONObject { + + private List parts; + private int marker; + + /** + * Constructs an BoxFileUploadSessionPart object using an already parsed JSON object. + * @param jsonObject the parsed JSON object. + */ + BoxFileUploadSessionPartList(JsonObject jsonObject) { + super(jsonObject); + } + + public List getParts() { + return this.parts; + } + + public int getMarker() { + return this.marker; + } + + protected void parseJSONMember(JsonObject.Member member) { + String memberName = member.getName(); + JsonValue value = member.getValue(); + if (memberName.equals("parts")) { + JsonArray array = (JsonArray) value; + + if (array.size() > 0) { + this.parts = this.getParts(array); + } + } else if (memberName.equals("marker")) { + this.marker = Double.valueOf(value.toString()).intValue(); + } + } + + private List getParts(JsonArray partsArray) { + List parts = new ArrayList(); + for (JsonValue value: partsArray) { + BoxFileUploadSessionPart part = new BoxFileUploadSessionPart((JsonObject) value); + parts.add(part); + } + + return parts; + } +} From 692868b16436a758fa24ae4011a34a670ecc10a5 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Sun, 26 Feb 2017 23:05:25 -0800 Subject: [PATCH 091/119] Upload session list parts and commit are added --- .../com/box/sdk/BoxFileUploadSession.java | 62 +++++++++++++------ 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index 7cae4c53f..396f35042 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -8,16 +8,18 @@ import java.security.NoSuchAlgorithmException; import java.text.ParseException; import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Random; import com.box.sdk.http.ContentType; 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; -/** - * - */ +@BoxResourceType("upload-session") public class BoxFileUploadSession extends BoxResource { private static final String DIGEST_HEADER_PREFIX_SHA = "sha="; @@ -135,14 +137,6 @@ public class Endpoints extends BoxJSONObject { private URL statusEndpoint; private URL abortEndpoint; - public Endpoints() { - super(); - } - - public Endpoints(String json) { - super(json); - } - Endpoints(JsonObject jsonObject) { super(jsonObject); } @@ -194,7 +188,8 @@ public void uploadPart(String partId, byte[] bytes, long startRange, Long partSi throws MalformedURLException, NoSuchAlgorithmException { URL uploadPartURL = this.sessionInfo.getSessionEndpoints().getUploadPartEndpoint(); - BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), uploadPartURL, HttpMethod.POST); + + BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), uploadPartURL, HttpMethod.PUT); request.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_OCTET_STREAM); request.addHeader(HttpHeaders.X_BOX_PART_ID, partId); @@ -203,13 +198,12 @@ public void uploadPart(String partId, byte[] bytes, long startRange, Long partSi request.addHeader(HttpHeaders.DIGEST, DIGEST_HEADER_PREFIX_SHA + digest); request.addHeader(HttpHeaders.CONTENT_RANGE, "bytes " + startRange + "-" + (startRange + partSize - 1) + "/" + totalSizeOfFile); - request.addHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(bytes.length)); request.setBody(new ByteArrayInputStream(bytes)); - BoxJSONResponse response = (BoxJSONResponse) request.send(); + BoxAPIResponse response = (BoxAPIResponse) request.send(); } - public int listParts(String sessionId, int marker, int limit) { + public BoxFileUploadSessionPartList listParts(int marker, int limit) { URL listPartsURL = this.sessionInfo.getSessionEndpoints().getListPartsEndpoint(); URLTemplate template = new URLTemplate(listPartsURL.toString()); @@ -222,18 +216,21 @@ public int listParts(String sessionId, int marker, int limit) { BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, HttpMethod.GET); BoxJSONResponse response = (BoxJSONResponse) request.send(); JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); -// System.out.println("Response: " + jsonObject); + //System.out.println("List parts: " + jsonObject); - return response.getResponseCode(); + return new BoxFileUploadSessionPartList(jsonObject); } - public int commit(String sessionId, String digest, JsonObject jsonObject, String ifMatch, String ifNonMatch) { + public int commit(String digest, List parts, + Map attributes, String ifMatch, String ifNonMatch) { + URL commitURL = this.sessionInfo.getSessionEndpoints().getCommitEndpoint(); BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), commitURL, HttpMethod.POST); request.addHeader(HttpHeaders.DIGEST, DIGEST_HEADER_PREFIX_SHA + digest); request.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON); - request.setBody(jsonObject.toString()); + String body = this.getCommitBody(parts, attributes); + request.setBody(body); BoxAPIResponse response = request.send(); //System.out.println("Response: " + response.getResponseCode()); @@ -254,6 +251,31 @@ public int commit(String sessionId, String digest, JsonObject jsonObject, String return response.getResponseCode(); } + private String getCommitBody(List parts, Map attributes) { + JsonObject jsonObject = new JsonObject(); + + JsonArray array = new JsonArray(); + for (BoxFileUploadSessionPart part: parts) { + JsonObject partObj = new JsonObject(); + partObj.add("part_id", part.getPartId()); + partObj.add("offset", part.getOffset()); + partObj.add("size", part.getSize()); + + array.add(partObj); + } + jsonObject.add("parts", array); + + if (attributes != null) { + JsonObject attrObj = new JsonObject(); + for (String key: attributes.keySet()) { + attrObj.add(key, attributes.get(key)); + } + jsonObject.add("attributes", attrObj); + } + + return jsonObject.toString(); + } + public BoxFileUploadSession.Info getUploadSessionStatus() { URL statusURL = this.sessionInfo.getSessionEndpoints().getStatusEndpoint(); BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), statusURL, HttpMethod.GET); @@ -267,7 +289,7 @@ public BoxFileUploadSession.Info getUploadSessionStatus() { public void abortUploadSession() { URL abortURL = this.sessionInfo.getSessionEndpoints().getAbortEndpoint(); - BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), abortURL, "DELETE"); + BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), abortURL, HttpMethod.DELETE); BoxAPIResponse response = request.send(); //System.out.println("Abort session status: " + response.getResponseCode()); } From 042b49e7d0ea908487d78c73e92433bc5ac0d0f7 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Mon, 27 Feb 2017 11:20:31 -0800 Subject: [PATCH 092/119] Commit operation returning BoxFile --- .../com/box/sdk/BoxFileUploadSession.java | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index 396f35042..c057840aa 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -221,7 +221,7 @@ public BoxFileUploadSessionPartList listParts(int marker, int limit) { return new BoxFileUploadSessionPartList(jsonObject); } - public int commit(String digest, List parts, + public BoxFile.Info commit(String digest, List parts, Map attributes, String ifMatch, String ifNonMatch) { URL commitURL = this.sessionInfo.getSessionEndpoints().getCommitEndpoint(); @@ -232,23 +232,13 @@ public int commit(String digest, List parts, String body = this.getCommitBody(parts, attributes); request.setBody(body); - BoxAPIResponse response = request.send(); + BoxJSONResponse response = (BoxJSONResponse) request.send(); //System.out.println("Response: " + response.getResponseCode()); - try { - InputStream stream = response.getBody(); - int value = stream.read(); - StringBuffer buffer = new StringBuffer(); - while (value != -1) { - buffer.append(value); - value = stream.read(); - } - //System.out.println("Response: " + buffer.toString()); - } catch (Exception ex) { - ex.printStackTrace(); - } + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + BoxFile file = new BoxFile(this.getAPI(), jsonObject.get("id").toString()); - return response.getResponseCode(); + return file.new Info(jsonObject); } private String getCommitBody(List parts, Map attributes) { From 482d4c65c3ef5a273e6b23815f60a6b9343b08b1 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Mon, 27 Feb 2017 12:00:58 -0800 Subject: [PATCH 093/119] If-Match and If-None-Match headers implementation --- src/main/java/com/box/sdk/BoxFile.java | 1 - .../com/box/sdk/BoxFileUploadSession.java | 19 +++++++++++-------- src/main/java/com/box/sdk/BoxFolder.java | 1 - 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index bda7859e5..b9e31b834 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -871,7 +871,6 @@ public BoxFileUploadSession.Info createUploadSession(long fileSize) { BoxJSONResponse response = (BoxJSONResponse) request.send(); JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); - System.out.println("Response: " + jsonObject); String sessionId = jsonObject.get("upload_session_id").asString(); BoxFileUploadSession session = new BoxFileUploadSession(this.getAPI(), sessionId); diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index c057840aa..41d8a8bc3 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -1,7 +1,6 @@ package com.box.sdk; import java.io.ByteArrayInputStream; -import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.security.MessageDigest; @@ -10,7 +9,6 @@ import java.util.Date; import java.util.List; import java.util.Map; -import java.util.Random; import com.box.sdk.http.ContentType; import com.box.sdk.http.HttpHeaders; @@ -200,7 +198,7 @@ public void uploadPart(String partId, byte[] bytes, long startRange, Long partSi "bytes " + startRange + "-" + (startRange + partSize - 1) + "/" + totalSizeOfFile); request.setBody(new ByteArrayInputStream(bytes)); - BoxAPIResponse response = (BoxAPIResponse) request.send(); + request.send(); } public BoxFileUploadSessionPartList listParts(int marker, int limit) { @@ -216,24 +214,30 @@ public BoxFileUploadSessionPartList listParts(int marker, int limit) { BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, HttpMethod.GET); BoxJSONResponse response = (BoxJSONResponse) request.send(); JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); - //System.out.println("List parts: " + jsonObject); return new BoxFileUploadSessionPartList(jsonObject); } public BoxFile.Info commit(String digest, List parts, - Map attributes, String ifMatch, String ifNonMatch) { + Map attributes, String ifMatch, String ifNoneMatch) { URL commitURL = this.sessionInfo.getSessionEndpoints().getCommitEndpoint(); BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), commitURL, HttpMethod.POST); request.addHeader(HttpHeaders.DIGEST, DIGEST_HEADER_PREFIX_SHA + digest); request.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON); + if (ifMatch != null) { + request.addHeader(HttpHeaders.IF_MATCH, ifMatch); + } + + if (ifNoneMatch != null) { + request.addHeader(HttpHeaders.IF_NONE_MATCH, ifNoneMatch); + } + String body = this.getCommitBody(parts, attributes); request.setBody(body); BoxJSONResponse response = (BoxJSONResponse) request.send(); - //System.out.println("Response: " + response.getResponseCode()); JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); BoxFile file = new BoxFile(this.getAPI(), jsonObject.get("id").toString()); @@ -280,7 +284,6 @@ public BoxFileUploadSession.Info getUploadSessionStatus() { public void abortUploadSession() { URL abortURL = this.sessionInfo.getSessionEndpoints().getAbortEndpoint(); BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), abortURL, HttpMethod.DELETE); - BoxAPIResponse response = request.send(); - //System.out.println("Abort session status: " + response.getResponseCode()); + request.send(); } } diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index c2d933c78..dd10f4e4b 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -742,7 +742,6 @@ public BoxFileUploadSession.Info createUploadSession(String folderId, long fileS BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); BoxJSONResponse response = (BoxJSONResponse) request.send(); JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); - System.out.println("Response: " + jsonObject); String sessionId = jsonObject.get("upload_session_id").asString(); BoxFileUploadSession session = new BoxFileUploadSession(this.getAPI(), sessionId); From 29a3f8f5aeb9cc2b5f13117ad33e8b901901b838 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Mon, 27 Feb 2017 12:01:45 -0800 Subject: [PATCH 094/119] If-Match and If-None-Match headers implementation --- src/main/java/com/box/sdk/http/HttpHeaders.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/box/sdk/http/HttpHeaders.java b/src/main/java/com/box/sdk/http/HttpHeaders.java index ecb942aa1..f9b151afa 100644 --- a/src/main/java/com/box/sdk/http/HttpHeaders.java +++ b/src/main/java/com/box/sdk/http/HttpHeaders.java @@ -9,6 +9,8 @@ public final class HttpHeaders { public static final String CONTENT_RANGE = "Content-Range"; public static final String CONTENT_TYPE = "Content-Type"; public static final String DIGEST = "Digest"; + public static final String IF_MATCH = "If-Match"; + public static final String IF_NONE_MATCH = "If-None-Match"; public static final String X_BOX_PART_ID = "X-Box-Part-Id"; //Prevents instantiation From 76206aadf2f35272bc42271487a98960d7e82473 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Tue, 28 Feb 2017 16:26:59 -0800 Subject: [PATCH 095/119] Integration tests for the commit --- src/main/java/com/box/sdk/BoxAPIRequest.java | 2 +- .../com/box/sdk/BoxFileUploadSession.java | 7 +- src/main/java/com/box/sdk/BoxFolder.java | 4 +- src/test/java/com/box/sdk/BoxFileTest.java | 213 ++++++++++++++++-- src/test/java/com/box/sdk/BoxFolderTest.java | 64 ++++-- 5 files changed, 256 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxAPIRequest.java b/src/main/java/com/box/sdk/BoxAPIRequest.java index de7b3585a..62eb7cc6f 100644 --- a/src/main/java/com/box/sdk/BoxAPIRequest.java +++ b/src/main/java/com/box/sdk/BoxAPIRequest.java @@ -425,7 +425,7 @@ private BoxAPIResponse trySend(ProgressListener listener) { BoxAPIResponse response; if (contentType == null) { response = new BoxAPIResponse(connection); - } else if (contentType.contains("application/json")) { + } else if (contentType.contains("application/json") || contentType.contains("text/plain")) { response = new BoxJSONResponse(connection); } else { response = new BoxAPIResponse(connection); diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index 41d8a8bc3..21f512e20 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -182,7 +182,7 @@ protected void parseJSONMember(JsonObject.Member member) { } } - public void uploadPart(String partId, byte[] bytes, long startRange, Long partSize, long totalSizeOfFile) + public void uploadPart(String partId, byte[] bytes, long offset, Long partSize, long totalSizeOfFile) throws MalformedURLException, NoSuchAlgorithmException { URL uploadPartURL = this.sessionInfo.getSessionEndpoints().getUploadPartEndpoint(); @@ -195,7 +195,7 @@ public void uploadPart(String partId, byte[] bytes, long startRange, Long partSi String digest = Base64.encode(digestBytes); request.addHeader(HttpHeaders.DIGEST, DIGEST_HEADER_PREFIX_SHA + digest); request.addHeader(HttpHeaders.CONTENT_RANGE, - "bytes " + startRange + "-" + (startRange + partSize - 1) + "/" + totalSizeOfFile); + "bytes " + offset + "-" + (offset + partSize - 1) + "/" + totalSizeOfFile); request.setBody(new ByteArrayInputStream(bytes)); request.send(); @@ -238,9 +238,8 @@ public BoxFile.Info commit(String digest, List parts, request.setBody(body); BoxJSONResponse response = (BoxJSONResponse) request.send(); - JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); - BoxFile file = new BoxFile(this.getAPI(), jsonObject.get("id").toString()); + BoxFile file = new BoxFile(this.getAPI(), jsonObject.get("id").asString()); return file.new Info(jsonObject); } diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index dd10f4e4b..45d9c5d20 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -729,10 +729,10 @@ public void deleteMetadata(String templateName, String scope) { response.disconnect(); } - public BoxFileUploadSession.Info createUploadSession(String folderId, long fileSize, String fileName) { + public BoxFileUploadSession.Info createUploadSession(String fileName, long fileSize) { String queryString = new QueryStringBuilder() - .appendParam("folder_id", folderId) + .appendParam("folder_id", this.getID()) .appendParam("file_size", fileSize) .appendParam("file_name", fileName) .toString(); diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index 19ad7a168..d073c6e37 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -10,6 +10,8 @@ import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; +import java.security.DigestInputStream; +import java.security.MessageDigest; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -17,6 +19,8 @@ import java.util.Collection; import java.util.Date; import java.util.Iterator; +import java.util.List; +import java.util.Random; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; @@ -968,17 +972,182 @@ public void setCollectionsWithInfoSucceeds() { @Test @Category(IntegrationTest.class) - public void uploadSessionSucceeds() { + public void uploadSessionCommitFlowSuccess() throws Exception { BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); BoxFolder rootFolder = BoxFolder.getRootFolder(api); - String fileName = "[createUploadSessionSucceeds] Test File.txt"; + + BoxFile uploadedFile = null; + try { + String fileName = "Tamme-Lauri_tamm_suvepäeval.jpg"; + URL fileURL = this.getClass().getResource("/sample-files/" + fileName); + String filePath = URLDecoder.decode(fileURL.getFile(), "utf-8"); + File file = new File(filePath); + long fileSize = file.length(); + + //Create the session + BoxFileUploadSession.Info session = createFileUploadSession(rootFolder, fileName, fileSize); + + //Create the parts + MessageDigest fileDigest = uploadParts(uploadedFile, session, fileSize); + + //List the session parts + List parts = listUploadSessionParts(session.getResource()); + + byte[] digestBytes = fileDigest.digest(); + String digest = Base64.encode(digestBytes); + + //Verify the delete session + uploadedFile = commitSession(session.getResource(), digest, parts); + } finally { + if (uploadedFile != null) { + uploadedFile.delete(); + } + } + } + + private BoxFileUploadSession.Info createFileUploadSession(BoxFolder folder, String fileName, long fileSize) { + BoxFileUploadSession.Info session = folder.createUploadSession(fileName, fileSize); + Assert.assertNotNull(session.getUploadSessionId()); + Assert.assertNotNull(session.getSessionExpiresAt()); + Assert.assertNotNull(session.getPartSize()); + + BoxFileUploadSession.Endpoints endpoints = session.getSessionEndpoints(); + Assert.assertNotNull(endpoints); + Assert.assertNotNull(endpoints.getUploadPartEndpoint()); + Assert.assertNotNull(endpoints.getStatusEndpoint()); + Assert.assertNotNull(endpoints.getListPartsEndpoint()); + Assert.assertNotNull(endpoints.getCommitEndpoint()); + Assert.assertNotNull(endpoints.getAbortEndpoint()); + + return session; + } + + + @Test + @Category(IntegrationTest.class) + public void uploadSessionVersionCommitFlowSuccess() throws Exception { + BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); + BoxFolder rootFolder = BoxFolder.getRootFolder(api); + + BoxFile.Info imageFileInfo = createImageFile(rootFolder); + + BoxFile uploadedFile = imageFileInfo.getResource(); + try { + //Create the session + BoxFileUploadSession.Info session = createFileUploadSession(uploadedFile, imageFileInfo.getSize()); + + //Create the parts + MessageDigest fileDigest = uploadParts(uploadedFile, session, imageFileInfo.getSize()); + + //List the session parts + List parts = listUploadSessionParts(session.getResource()); + + byte[] digestBytes = fileDigest.digest(); + String digest = Base64.encode(digestBytes); + + //Verify the delete session + uploadedFile = commitSession(session.getResource(), digest, parts); + } finally { + uploadedFile.delete(); + } + } + + private BoxFileUploadSession.Info createFileUploadSession(BoxFile uploadedFile, long fileSize) { + BoxFileUploadSession.Info session = uploadedFile.createUploadSession(fileSize); + Assert.assertNotNull(session.getUploadSessionId()); + Assert.assertNotNull(session.getSessionExpiresAt()); + Assert.assertNotNull(session.getPartSize()); + + BoxFileUploadSession.Endpoints endpoints = session.getSessionEndpoints(); + Assert.assertNotNull(endpoints); + Assert.assertNotNull(endpoints.getUploadPartEndpoint()); + Assert.assertNotNull(endpoints.getStatusEndpoint()); + Assert.assertNotNull(endpoints.getListPartsEndpoint()); + Assert.assertNotNull(endpoints.getCommitEndpoint()); + Assert.assertNotNull(endpoints.getAbortEndpoint()); + + return session; + } + + private MessageDigest uploadParts(BoxFile uploadedFile, BoxFileUploadSession.Info session, + long fileSize) throws Exception { + + String fileName = "Tamme-Lauri_tamm_suvepäeval.jpg"; + URL fileURL = this.getClass().getResource("/sample-files/" + fileName); + String filePath = URLDecoder.decode(fileURL.getFile(), "utf-8"); + File file = new File(filePath); + + FileInputStream stream = new FileInputStream(file); + MessageDigest fileDigest = MessageDigest.getInstance("SHA1"); + DigestInputStream dis = new DigestInputStream(stream, fileDigest); + + long offset = 0; + byte[] bytes = null; + long processed = 0; + boolean canBreak = false; + while(true) { + long min = session.getPartSize(); + long diff = fileSize - processed; + if (diff < min) { + min = diff; + canBreak = true; + } + + bytes = new byte[(int) min]; + dis.read(bytes); + + session.getResource().uploadPart(generateHex(), bytes, offset, min, fileSize); + + offset = offset + session.getPartSize(); + processed += min; + if (canBreak) { + break; + } + } + + return fileDigest; + } + + private String generateHex() { + String hex = ""; + while (hex.length() != 8) { + Random random = new Random(); + int val = random.nextInt(); + hex = Integer.toHexString(val); + } + + return hex; + } + + private BoxFile.Info createImageFile(BoxFolder folder) throws IOException { + String fileName = "Tamme-Lauri_tamm_suvepäeval.jpg"; + URL fileURL = this.getClass().getResource("/sample-files/" + fileName); + String filePath = URLDecoder.decode(fileURL.getFile(), "utf-8"); + File file = new File(filePath); + long fileSize = file.length(); + + FileInputStream stream = new FileInputStream(file); + + byte[] fileBytes = new byte[(int) fileSize]; + + InputStream uploadStream = new ByteArrayInputStream(fileBytes); + return folder.uploadFile(uploadStream, fileName); + } + + @Test + @Category(IntegrationTest.class) + public void uploadSessionAbortFlowSuccess() throws Exception { + BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); + BoxFolder rootFolder = BoxFolder.getRootFolder(api); + + String fileName = "[setCollectionsWithInfoSucceeds] Test File.txt"; String fileContent = "Test file"; byte[] fileBytes = fileContent.getBytes(StandardCharsets.UTF_8); InputStream uploadStream = new ByteArrayInputStream(fileBytes); BoxFile uploadedFile = rootFolder.uploadFile(uploadStream, fileName).getResource(); try { - BoxFileUploadSession session = uploadedFile.createUploadSession(10000000); + BoxFileUploadSession.Info session = uploadedFile.createUploadSession(fileBytes.length); Assert.assertNotNull(session.getUploadSessionId()); Assert.assertNotNull(session.getSessionExpiresAt()); Assert.assertNotNull(session.getPartSize()); @@ -992,28 +1161,44 @@ public void uploadSessionSucceeds() { Assert.assertNotNull(endpoints.getAbortEndpoint()); //Verify the status of the session - getUploadSessionStatusSucceeds(uploadedFile, session.getUploadSessionId()); + getUploadSessionStatus(session.getResource()); //Verify the delete session - abortUploadSessionStatusSucceeds(uploadedFile, session.getUploadSessionId()); + abortUploadSession(session.getResource()); } finally { uploadedFile.delete(); } } - private void getUploadSessionStatusSucceeds(BoxFile file, String sessionId) { - BoxFileUploadSession session = file.getUploadSessionStatus(sessionId); - Assert.assertNotNull(session.getSessionExpiresAt()); - Assert.assertNotNull(session.getPartSize()); - Assert.assertNotNull(session.getTotalParts()); - Assert.assertNotNull(session.getPartsProcessed()); + private List listUploadSessionParts(BoxFileUploadSession session) { + BoxFileUploadSessionPartList list = session.listParts(0, 10); + + List parts = list.getParts(); + Assert.assertEquals(parts.size(), 3); + + return parts; + } + + private BoxFile commitSession(BoxFileUploadSession session, String digest, List parts) { + BoxFile.Info file = session.commit(digest, parts, null, null, null); + Assert.assertNotNull(file); + + return file.getResource(); + } + + private void getUploadSessionStatus(BoxFileUploadSession session) { + BoxFileUploadSession.Info sessionInfo = session.getUploadSessionStatus(); + Assert.assertNotNull(sessionInfo.getSessionExpiresAt()); + Assert.assertNotNull(sessionInfo.getPartSize()); + Assert.assertNotNull(sessionInfo.getTotalParts()); + Assert.assertNotNull(sessionInfo.getPartsProcessed()); } - private void abortUploadSessionStatusSucceeds(BoxFile file, String sessionId) { - file.abortUploadSession(sessionId); + private void abortUploadSession(BoxFileUploadSession session) { + session.abortUploadSession(); try { - BoxFileUploadSession session = file.getUploadSessionStatus(sessionId); + BoxFileUploadSession.Info sessionInfo = session.getUploadSessionStatus(); //If the session is aborted, this line should not be executed. Assert.assertFalse("Upload session is not deleted", true); diff --git a/src/test/java/com/box/sdk/BoxFolderTest.java b/src/test/java/com/box/sdk/BoxFolderTest.java index c24f22642..5da3f0e12 100644 --- a/src/test/java/com/box/sdk/BoxFolderTest.java +++ b/src/test/java/com/box/sdk/BoxFolderTest.java @@ -1068,20 +1068,58 @@ public void sharedLinkInfoHasEffectiveAccess() { @Test @Category(IntegrationTest.class) - public void createUploadSessionSucceeds() { + public void uploadSessionAbortFlowSuccess() throws Exception { BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); BoxFolder rootFolder = BoxFolder.getRootFolder(api); - BoxFileUploadSession session = rootFolder.createUploadSession("0", 1000000, "Test_File.txt"); - Assert.assertNotNull(session.getUploadSessionId()); - Assert.assertNotNull(session.getSessionExpiresAt()); - Assert.assertNotNull(session.getPartSize()); - - BoxFileUploadSession.Endpoints endpoints = session.getSessionEndpoints(); - Assert.assertNotNull(endpoints); - Assert.assertNotNull(endpoints.getUploadPartEndpoint()); - Assert.assertNotNull(endpoints.getStatusEndpoint()); - Assert.assertNotNull(endpoints.getListPartsEndpoint()); - Assert.assertNotNull(endpoints.getCommitEndpoint()); - Assert.assertNotNull(endpoints.getAbortEndpoint()); + + String fileName = "[setCollectionsWithInfoSucceeds] Test File.txt"; + String fileContent = "Test file"; + byte[] fileBytes = fileContent.getBytes(StandardCharsets.UTF_8); + + InputStream uploadStream = new ByteArrayInputStream(fileBytes); + BoxFile uploadedFile = rootFolder.uploadFile(uploadStream, fileName).getResource(); + try { + BoxFileUploadSession.Info session = uploadedFile.createUploadSession(fileBytes.length); + Assert.assertNotNull(session.getUploadSessionId()); + Assert.assertNotNull(session.getSessionExpiresAt()); + Assert.assertNotNull(session.getPartSize()); + + BoxFileUploadSession.Endpoints endpoints = session.getSessionEndpoints(); + Assert.assertNotNull(endpoints); + Assert.assertNotNull(endpoints.getUploadPartEndpoint()); + Assert.assertNotNull(endpoints.getStatusEndpoint()); + Assert.assertNotNull(endpoints.getListPartsEndpoint()); + Assert.assertNotNull(endpoints.getCommitEndpoint()); + Assert.assertNotNull(endpoints.getAbortEndpoint()); + + //Verify the status of the session + getUploadSessionStatus(session.getResource()); + + //Verify the delete session + abortUploadSession(session.getResource()); + } finally { + uploadedFile.delete(); + } + } + + private void getUploadSessionStatus(BoxFileUploadSession session) { + BoxFileUploadSession.Info sessionInfo = session.getUploadSessionStatus(); + Assert.assertNotNull(sessionInfo.getSessionExpiresAt()); + Assert.assertNotNull(sessionInfo.getPartSize()); + Assert.assertNotNull(sessionInfo.getTotalParts()); + Assert.assertNotNull(sessionInfo.getPartsProcessed()); + } + + private void abortUploadSession(BoxFileUploadSession session) { + session.abortUploadSession(); + + try { + BoxFileUploadSession.Info sessionInfo = session.getUploadSessionStatus(); + + //If the session is aborted, this line should not be executed. + Assert.assertFalse("Upload session is not deleted", true); + } catch(BoxAPIException apiEx) { + Assert.assertEquals(apiEx.getResponseCode(), 404); + } } } From 3a8b3ee1556313b688ea9733b9cfaa1a2b65999e Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Tue, 28 Feb 2017 17:25:56 -0800 Subject: [PATCH 096/119] public api support for upload file is moved to 2.0 from 2.1 --- .../java/com/box/sdk/BoxAPIConnection.java | 2 +- src/main/java/com/box/sdk/BoxFile.java | 8 +++++--- .../com/box/sdk/BoxFileUploadSession.java | 19 ++++++++++++++++--- src/main/java/com/box/sdk/BoxFolder.java | 16 ++++++++-------- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxAPIConnection.java b/src/main/java/com/box/sdk/BoxAPIConnection.java index 10bf2f145..ed6639b51 100644 --- a/src/main/java/com/box/sdk/BoxAPIConnection.java +++ b/src/main/java/com/box/sdk/BoxAPIConnection.java @@ -26,7 +26,7 @@ public class BoxAPIConnection { private static final String TOKEN_URL_STRING = "https://api.box.com/oauth2/token"; private static final String DEFAULT_BASE_URL = "https://api.box.com/2.0/"; private static final String DEFAULT_BASE_UPLOAD_URL = "https://upload.box.com/api/2.0/"; - private static final String DEFAULT_BASE_UPLOAD_SESSION_URL = "https://upload.app.box.com/api/2.1/"; + private static final String DEFAULT_BASE_UPLOAD_SESSION_URL = "https://upload.app.box.com/api/2.0/"; /** * The amount of buffer time, in milliseconds, to use when determining if an access token should be refreshed. For diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index b9e31b834..298a94d25 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -862,13 +862,15 @@ public BoxFile.Info setCollections(BoxCollection... collections) { } public BoxFileUploadSession.Info createUploadSession(long fileSize) { - String queryString = new QueryStringBuilder().appendParam("file_size", fileSize).toString(); - URL url = UPLOAD_SESSION_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseUploadSessionURL(), - queryString, this.getID()); + URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), this.getID()); BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); request.addHeader("Content-Type", "application/json"); + JsonObject body = new JsonObject(); + body.add("file_size", fileSize); + request.setBody(body.toString()); + BoxJSONResponse response = (BoxJSONResponse) request.send(); JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index 21f512e20..8ee6f7f9e 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -237,11 +237,24 @@ public BoxFile.Info commit(String digest, List parts, String body = this.getCommitBody(parts, attributes); request.setBody(body); - BoxJSONResponse response = (BoxJSONResponse) request.send(); + BoxAPIResponse response = (BoxAPIResponse) request.send(); + if (response instanceof BoxJSONResponse) { + return getFile((BoxJSONResponse) response); + } else { + throw new BoxAPIException("Commit response content type is not application/json. The response code : " + response.getResponseCode()); + } + } + + private BoxFile.Info getFile(BoxJSONResponse response) { JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); - BoxFile file = new BoxFile(this.getAPI(), jsonObject.get("id").asString()); + System.out.println("Response: " + response.getResponseCode() + " : " + response.getJSON()); + + JsonArray array = (JsonArray) jsonObject.get("entries"); + JsonObject fileObj = (JsonObject) array.get(0); + + BoxFile file = new BoxFile(this.getAPI(), fileObj.get("id").asString()); - return file.new Info(jsonObject); + return file.new Info(fileObj); } private String getCommitBody(List parts, Map attributes) { diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index 45d9c5d20..87867bb6e 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -731,15 +731,15 @@ public void deleteMetadata(String templateName, String scope) { public BoxFileUploadSession.Info createUploadSession(String fileName, long fileSize) { - String queryString = new QueryStringBuilder() - .appendParam("folder_id", this.getID()) - .appendParam("file_size", fileSize) - .appendParam("file_name", fileName) - .toString(); - - URL url = UPLOAD_SESSION_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseUploadSessionURL(), - queryString); + URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL()); BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); + + JsonObject body = new JsonObject(); + body.add("folder_id", this.getID()); + body.add("file_name", fileName); + body.add("file_size", fileSize); + request.setBody(body.toString()); + BoxJSONResponse response = (BoxJSONResponse) request.send(); JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); From 93582fae86042d704102d71aa22b1c988d81eb03 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Wed, 1 Mar 2017 14:13:50 -0800 Subject: [PATCH 097/119] upload large file method is added --- src/main/java/com/box/sdk/BoxFile.java | 6 + src/main/java/com/box/sdk/BoxFolder.java | 6 + .../java/com/box/sdk/LargeFileUpload.java | 153 ++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 src/main/java/com/box/sdk/LargeFileUpload.java diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 298a94d25..b1bf9bf36 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -879,6 +879,12 @@ public BoxFileUploadSession.Info createUploadSession(long fileSize) { return session.new Info(jsonObject); } + public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize) throws Exception { + URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), this.getID()); + + return LargeFileUpload.upload(this.getAPI(), inputStream, url, fileSize); + } + /** * Contains information about a BoxFile. */ diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index 87867bb6e..cf49647d3 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -749,6 +749,12 @@ public BoxFileUploadSession.Info createUploadSession(String fileName, long fileS return session.new Info(jsonObject); } + public BoxFile.Info uploadLargeFile(InputStream inputStream, String fileName, long fileSize) throws Exception { + URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL()); + + return LargeFileUpload.upload(this.getAPI(), this.getID(), inputStream, url, fileName, fileSize); + } + /** * Contains information about a BoxFolder. */ diff --git a/src/main/java/com/box/sdk/LargeFileUpload.java b/src/main/java/com/box/sdk/LargeFileUpload.java new file mode 100644 index 000000000..159c52687 --- /dev/null +++ b/src/main/java/com/box/sdk/LargeFileUpload.java @@ -0,0 +1,153 @@ +package com.box.sdk; + +import com.box.sdk.*; +import com.box.sdk.http.ContentType; +import com.box.sdk.http.HttpHeaders; +import com.box.sdk.http.HttpMethod; +import com.eclipsesource.json.JsonObject; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Random; + +/** + * + */ +public final class LargeFileUpload { + + private static final String DIGEST_HEADER_PREFIX_SHA = "sha="; + private static final String DIGEST_ALGORITHM_SHA1 = "SHA1"; + + private static final String MARKER_QUERY_STRING = "marker"; + private static final String LIMIT_QUERY_STRING = "limit"; + + private LargeFileUpload() { + } + + static BoxFile.Info upload(BoxAPIConnection boxApi, String folderId, InputStream stream, URL url, + String fileName, long fileSize) throws Exception { + + BoxFileUploadSession.Info session = createUploadSession(boxApi, folderId, url, fileName, fileSize); + + MessageDigest digest = uploadParts(session, stream, fileSize); + byte[] digestBytes = digest.digest(); + String digestStr = Base64.encode(digestBytes); + + BoxFileUploadSessionPartList list = session.getResource().listParts(0, 1000); + try { + return session.getResource().commit(digestStr, list.getParts(), null, null, null); + } finally { + session.getResource().abortUploadSession(); + } + } + + private static BoxFileUploadSession.Info createUploadSession(BoxAPIConnection boxApi, String folderId, + URL url, String fileName, long fileSize) { + + BoxJSONRequest request = new BoxJSONRequest(boxApi, url, HttpMethod.POST); + + JsonObject body = new JsonObject(); + body.add("folder_id", folderId); + body.add("file_name", fileName); + body.add("file_size", fileSize); + request.setBody(body.toString()); + + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + + String sessionId = jsonObject.get("upload_session_id").asString(); + BoxFileUploadSession session = new BoxFileUploadSession(boxApi, sessionId); + + return session.new Info(jsonObject); + } + + static BoxFile.Info upload(BoxAPIConnection boxApi, InputStream stream, URL url, long fileSize) throws Exception { + BoxFileUploadSession.Info session = createUploadSession(boxApi, url, fileSize); + + MessageDigest digest = uploadParts(session, stream, fileSize); + byte[] digestBytes = digest.digest(); + String digestStr = Base64.encode(digestBytes); + + BoxFileUploadSessionPartList list = session.getResource().listParts(0, 1000); + try { + return session.getResource().commit(digestStr, list.getParts(), null, null, null); + } finally { + session.getResource().abortUploadSession(); + } + } + + private static BoxFileUploadSession.Info createUploadSession(BoxAPIConnection boxApi, URL url, long fileSize) { + BoxJSONRequest request = new BoxJSONRequest(boxApi, url, HttpMethod.POST); + JsonObject body = new JsonObject(); + body.add("file_size", fileSize); + request.setBody(body.toString()); + + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + + String sessionId = jsonObject.get("upload_session_id").asString(); + BoxFileUploadSession session = new BoxFileUploadSession(boxApi, sessionId); + + return session.new Info(jsonObject); + } + + private static MessageDigest uploadParts(BoxFileUploadSession.Info session, InputStream stream, long fileSize) + throws Exception { + + MessageDigest digest = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1); + DigestInputStream dis = new DigestInputStream(stream, digest); + + long partSize = session.getPartSize(); + long offset = 0; + long processed = 0; + while (processed < fileSize) { + long diff = fileSize - processed; + if (diff < partSize) { + partSize = diff; + } + + uploadPart(session.getResource(), dis, offset, partSize, fileSize); + + processed += partSize; + offset += partSize; + } + + return digest; + } + + private static void uploadPart(BoxFileUploadSession session, InputStream stream, long offset, + long partSize, long fileSize) throws Exception { + + String partId = generateHex(); + byte[] bytes = new byte[(int) partSize]; + stream.read(bytes); + + for(int i = 0; i < 3; i++) { + try { + session.uploadPart(partId, bytes, offset, partSize, fileSize); + break; + } catch (BoxAPIException ex) { + if (i == 2) { + throw ex; + } + } + } + } + + public static String generateHex() { + String hex = ""; + while (hex.length() != 8) { + Random random = new Random(); + int val = random.nextInt(); + hex = Integer.toHexString(val); + } + + return hex; + } + +} From 51885a06ed656499723cbc3508d91d8c1fb91a27 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Wed, 1 Mar 2017 15:30:30 -0800 Subject: [PATCH 098/119] upload part takes stream as input --- .../com/box/sdk/BoxFileUploadSession.java | 9 ++++- .../java/com/box/sdk/LargeFileUpload.java | 4 +- src/test/java/com/box/sdk/BoxFileTest.java | 39 +++++++++++++++++-- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index 8ee6f7f9e..f04509f8d 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -1,6 +1,8 @@ package com.box.sdk; import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.security.MessageDigest; @@ -182,8 +184,8 @@ protected void parseJSONMember(JsonObject.Member member) { } } - public void uploadPart(String partId, byte[] bytes, long offset, Long partSize, long totalSizeOfFile) - throws MalformedURLException, NoSuchAlgorithmException { + public void uploadPart(String partId, InputStream stream, long offset, long partSize, long totalSizeOfFile) + throws IOException, NoSuchAlgorithmException { URL uploadPartURL = this.sessionInfo.getSessionEndpoints().getUploadPartEndpoint(); @@ -191,6 +193,9 @@ public void uploadPart(String partId, byte[] bytes, long offset, Long partSize, request.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_OCTET_STREAM); request.addHeader(HttpHeaders.X_BOX_PART_ID, partId); + byte[] bytes = new byte[(int) partSize]; + stream.read(bytes); + byte[] digestBytes = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1).digest(bytes); String digest = Base64.encode(digestBytes); request.addHeader(HttpHeaders.DIGEST, DIGEST_HEADER_PREFIX_SHA + digest); diff --git a/src/main/java/com/box/sdk/LargeFileUpload.java b/src/main/java/com/box/sdk/LargeFileUpload.java index 159c52687..c6f3a77a3 100644 --- a/src/main/java/com/box/sdk/LargeFileUpload.java +++ b/src/main/java/com/box/sdk/LargeFileUpload.java @@ -124,12 +124,10 @@ private static void uploadPart(BoxFileUploadSession session, InputStream stream, long partSize, long fileSize) throws Exception { String partId = generateHex(); - byte[] bytes = new byte[(int) partSize]; - stream.read(bytes); for(int i = 0; i < 3; i++) { try { - session.uploadPart(partId, bytes, offset, partSize, fileSize); + session.uploadPart(partId, stream, offset, partSize, fileSize); break; } catch (BoxAPIException ex) { if (i == 2) { diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index d073c6e37..c6a0620fd 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -1093,10 +1093,7 @@ private MessageDigest uploadParts(BoxFile uploadedFile, BoxFileUploadSession.Inf canBreak = true; } - bytes = new byte[(int) min]; - dis.read(bytes); - - session.getResource().uploadPart(generateHex(), bytes, offset, min, fileSize); + session.getResource().uploadPart(generateHex(), dis, offset, min, fileSize); offset = offset + session.getPartSize(); processed += min; @@ -1213,4 +1210,38 @@ private static byte[] readAllBytes(String fileName) throws IOException { f.read(b); return b; } + + @Test + @Category(IntegrationTest.class) + public void uploadLargeFile() throws Exception { + File file = new File("/Users/kshanmugasundaram/Downloads/tenmb"); + FileInputStream stream = new FileInputStream(file); + + BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); + BoxFolder rootFolder = BoxFolder.getRootFolder(api); + BoxFile.Info fileUploaded = rootFolder.uploadLargeFile(stream, "tenmb", file.length()); + Assert.assertNotNull(fileUploaded); + + fileUploaded.getResource().delete(); + } + + @Test + @Category(IntegrationTest.class) + public void uploadLargeFileVersion() throws Exception { + String fileName = "Tamme-Lauri_tamm_suvepäeval.jpg"; + URL fileURL = this.getClass().getResource("/sample-files/" + fileName); + String filePath = URLDecoder.decode(fileURL.getFile(), "utf-8"); + File file = new File(filePath); + FileInputStream stream = new FileInputStream(file); + + BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); + BoxFolder rootFolder = BoxFolder.getRootFolder(api); + BoxFile.Info uploadedFile = rootFolder.uploadFile(stream, "tenmb"); + + stream = new FileInputStream(file); + BoxFile.Info fileVerion = uploadedFile.getResource().uploadLargeFile(stream, file.length()); + Assert.assertNotNull(fileVerion); + + fileVerion.getResource().delete(); + } } From 8de7f8d6aba2cf9fa6d9a5e8a50ef9afed8778d5 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Wed, 1 Mar 2017 16:01:42 -0800 Subject: [PATCH 099/119] Checkstyle clean up --- .../java/com/box/sdk/BoxFileUploadSession.java | 7 ++++--- src/main/java/com/box/sdk/LargeFileUpload.java | 14 ++++---------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index f04509f8d..42fe7acb2 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -242,11 +242,12 @@ public BoxFile.Info commit(String digest, List parts, String body = this.getCommitBody(parts, attributes); request.setBody(body); - BoxAPIResponse response = (BoxAPIResponse) request.send(); + BoxAPIResponse response = request.send(); if (response instanceof BoxJSONResponse) { - return getFile((BoxJSONResponse) response); + return this.getFile((BoxJSONResponse) response); } else { - throw new BoxAPIException("Commit response content type is not application/json. The response code : " + response.getResponseCode()); + throw new BoxAPIException("Commit response content type is not application/json. The response code : " + + response.getResponseCode()); } } diff --git a/src/main/java/com/box/sdk/LargeFileUpload.java b/src/main/java/com/box/sdk/LargeFileUpload.java index c6f3a77a3..c76d93397 100644 --- a/src/main/java/com/box/sdk/LargeFileUpload.java +++ b/src/main/java/com/box/sdk/LargeFileUpload.java @@ -1,20 +1,14 @@ package com.box.sdk; -import com.box.sdk.*; -import com.box.sdk.http.ContentType; -import com.box.sdk.http.HttpHeaders; -import com.box.sdk.http.HttpMethod; -import com.eclipsesource.json.JsonObject; - -import java.io.ByteArrayInputStream; import java.io.InputStream; -import java.net.MalformedURLException; import java.net.URL; import java.security.DigestInputStream; import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.Random; +import com.box.sdk.http.HttpMethod; +import com.eclipsesource.json.JsonObject; + /** * */ @@ -125,7 +119,7 @@ private static void uploadPart(BoxFileUploadSession session, InputStream stream, String partId = generateHex(); - for(int i = 0; i < 3; i++) { + for (int i = 0; i < 3; i++) { try { session.uploadPart(partId, stream, offset, partSize, fileSize); break; From 479d263c2f455c79fa67c842be37497ced857897 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Thu, 2 Mar 2017 10:03:24 -0800 Subject: [PATCH 100/119] Upload part signature change and 202 response code fix --- src/main/java/com/box/sdk/BoxAPIResponse.java | 9 +++++ .../com/box/sdk/BoxFileUploadSession.java | 25 ++++++++++++-- .../java/com/box/sdk/LargeFileUpload.java | 33 +++++++++++-------- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxAPIResponse.java b/src/main/java/com/box/sdk/BoxAPIResponse.java index 92f376661..424ae94b5 100644 --- a/src/main/java/com/box/sdk/BoxAPIResponse.java +++ b/src/main/java/com/box/sdk/BoxAPIResponse.java @@ -89,6 +89,15 @@ public long getContentLength() { return this.connection.getContentLength(); } + /** + * Gets the value of the given header field. + * @param fieldName name of the header field. + * @return value of the header. + */ + public String getHeaderField(String fieldName) { + return this.connection.getHeaderField(fieldName); + } + /** * Gets an InputStream for reading this response's body. * @return an InputStream for reading the response's body. diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index 42fe7acb2..05ab31a35 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -184,8 +184,8 @@ protected void parseJSONMember(JsonObject.Member member) { } } - public void uploadPart(String partId, InputStream stream, long offset, long partSize, long totalSizeOfFile) - throws IOException, NoSuchAlgorithmException { + public BoxFileUploadSessionPart uploadPart(String partId, InputStream stream, long offset, long partSize, + long totalSizeOfFile) throws IOException, NoSuchAlgorithmException { URL uploadPartURL = this.sessionInfo.getSessionEndpoints().getUploadPartEndpoint(); @@ -204,6 +204,13 @@ public void uploadPart(String partId, InputStream stream, long offset, long part request.setBody(new ByteArrayInputStream(bytes)); request.send(); + + BoxFileUploadSessionPart part = new BoxFileUploadSessionPart(); + part.setPartId(partId); + part.setOffset(offset); + part.setSize(partSize); + + return part; } public BoxFileUploadSessionPartList listParts(int marker, int limit) { @@ -243,6 +250,19 @@ public BoxFile.Info commit(String digest, List parts, request.setBody(body); BoxAPIResponse response = request.send(); + if (response.getResponseCode() == 202) { + String retryInterval = response.getHeaderField("retry-after"); + if (retryInterval != null) { + try { + Thread.sleep(new Integer(retryInterval) * 1000); + } catch (InterruptedException ie) { + throw new BoxAPIException("Commit retry failed. ", ie); + } + + return this.commit(digest, parts, attributes, ifMatch, ifNoneMatch); + } + } + if (response instanceof BoxJSONResponse) { return this.getFile((BoxJSONResponse) response); } else { @@ -253,7 +273,6 @@ public BoxFile.Info commit(String digest, List parts, private BoxFile.Info getFile(BoxJSONResponse response) { JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); - System.out.println("Response: " + response.getResponseCode() + " : " + response.getJSON()); JsonArray array = (JsonArray) jsonObject.get("entries"); JsonObject fileObj = (JsonObject) array.get(0); diff --git a/src/main/java/com/box/sdk/LargeFileUpload.java b/src/main/java/com/box/sdk/LargeFileUpload.java index c76d93397..8024cf749 100644 --- a/src/main/java/com/box/sdk/LargeFileUpload.java +++ b/src/main/java/com/box/sdk/LargeFileUpload.java @@ -4,6 +4,8 @@ import java.net.URL; import java.security.DigestInputStream; import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.List; import java.util.Random; import com.box.sdk.http.HttpMethod; @@ -28,13 +30,15 @@ static BoxFile.Info upload(BoxAPIConnection boxApi, String folderId, InputStream BoxFileUploadSession.Info session = createUploadSession(boxApi, folderId, url, fileName, fileSize); - MessageDigest digest = uploadParts(session, stream, fileSize); + MessageDigest digest = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1); + List parts = uploadParts(session, stream, fileSize, digest); + byte[] digestBytes = digest.digest(); String digestStr = Base64.encode(digestBytes); BoxFileUploadSessionPartList list = session.getResource().listParts(0, 1000); try { - return session.getResource().commit(digestStr, list.getParts(), null, null, null); + return session.getResource().commit(digestStr, parts, null, null, null); } finally { session.getResource().abortUploadSession(); } @@ -63,13 +67,15 @@ private static BoxFileUploadSession.Info createUploadSession(BoxAPIConnection bo static BoxFile.Info upload(BoxAPIConnection boxApi, InputStream stream, URL url, long fileSize) throws Exception { BoxFileUploadSession.Info session = createUploadSession(boxApi, url, fileSize); - MessageDigest digest = uploadParts(session, stream, fileSize); + MessageDigest digest = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1); + List parts = uploadParts(session, stream, fileSize, digest); + byte[] digestBytes = digest.digest(); String digestStr = Base64.encode(digestBytes); BoxFileUploadSessionPartList list = session.getResource().listParts(0, 1000); try { - return session.getResource().commit(digestStr, list.getParts(), null, null, null); + return session.getResource().commit(digestStr, parts, null, null, null); } finally { session.getResource().abortUploadSession(); } @@ -90,11 +96,11 @@ private static BoxFileUploadSession.Info createUploadSession(BoxAPIConnection bo return session.new Info(jsonObject); } - private static MessageDigest uploadParts(BoxFileUploadSession.Info session, InputStream stream, long fileSize) - throws Exception { + private static List uploadParts(BoxFileUploadSession.Info session, InputStream stream, + long fileSize, MessageDigest digest) throws Exception { - MessageDigest digest = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1); DigestInputStream dis = new DigestInputStream(stream, digest); + List parts = new ArrayList(); long partSize = session.getPartSize(); long offset = 0; @@ -105,30 +111,32 @@ private static MessageDigest uploadParts(BoxFileUploadSession.Info session, Inpu partSize = diff; } - uploadPart(session.getResource(), dis, offset, partSize, fileSize); + BoxFileUploadSessionPart part = uploadPart(session.getResource(), dis, offset, partSize, fileSize); + parts.add(part); processed += partSize; offset += partSize; } - return digest; + return parts; } - private static void uploadPart(BoxFileUploadSession session, InputStream stream, long offset, + private static BoxFileUploadSessionPart uploadPart(BoxFileUploadSession session, InputStream stream, long offset, long partSize, long fileSize) throws Exception { String partId = generateHex(); for (int i = 0; i < 3; i++) { try { - session.uploadPart(partId, stream, offset, partSize, fileSize); - break; + return session.uploadPart(partId, stream, offset, partSize, fileSize); } catch (BoxAPIException ex) { if (i == 2) { throw ex; } } } + + return null; } public static String generateHex() { @@ -141,5 +149,4 @@ public static String generateHex() { return hex; } - } From 572db0e3650be7bd7f827f94181f763bc086c648 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Thu, 2 Mar 2017 13:25:38 -0800 Subject: [PATCH 101/119] Added Javadoc --- src/main/java/com/box/sdk/BoxAPIRequest.java | 6 + src/main/java/com/box/sdk/BoxFile.java | 14 +- .../com/box/sdk/BoxFileUploadSession.java | 161 ++++++++++++++---- .../com/box/sdk/BoxFileUploadSessionPart.java | 37 ++-- .../box/sdk/BoxFileUploadSessionPartList.java | 15 +- src/main/java/com/box/sdk/BoxFolder.java | 16 +- .../java/com/box/sdk/LargeFileUpload.java | 105 +++++++++++- .../java/com/box/sdk/http/ContentType.java | 9 +- .../java/com/box/sdk/http/HttpHeaders.java | 31 +++- .../java/com/box/sdk/http/HttpMethod.java | 41 ++++- 10 files changed, 379 insertions(+), 56 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxAPIRequest.java b/src/main/java/com/box/sdk/BoxAPIRequest.java index 62eb7cc6f..036da1671 100644 --- a/src/main/java/com/box/sdk/BoxAPIRequest.java +++ b/src/main/java/com/box/sdk/BoxAPIRequest.java @@ -78,6 +78,12 @@ public BoxAPIRequest(BoxAPIConnection api, URL url, String method) { this.addHeader("Accept-Charset", "utf-8"); } + /** + * 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 method the HTTP method of the request. + */ public BoxAPIRequest(BoxAPIConnection api, URL uploadPartEndpoint, HttpMethod method) { this(api, uploadPartEndpoint, method.name()); } diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index b1bf9bf36..b49814677 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -861,6 +861,12 @@ public BoxFile.Info setCollections(BoxCollection... collections) { return new Info(jsonObject); } + /** + * Creates an upload session to create a new version of a file in chunks. + * This will first verify that the version can be created and then open a session for uploading pieces of the file. + * @param fileSize the size of the file that will be uploaded. + * @return the created upload session instance. + */ public BoxFileUploadSession.Info createUploadSession(long fileSize) { URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), this.getID()); @@ -879,7 +885,13 @@ public BoxFileUploadSession.Info createUploadSession(long fileSize) { return session.new Info(jsonObject); } - public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize) throws Exception { + /** + * Creates a new version of a file. + * @param inputStream the stream instance that contains the data. + * @param fileSize the size of the file that will be uploaded. + * @return the created file instance. + */ + public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize) { URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), this.getID()); return LargeFileUpload.upload(this.getAPI(), inputStream, url, fileSize); diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index 05ab31a35..995c5268f 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -19,6 +19,12 @@ import com.eclipsesource.json.JsonObject; import com.eclipsesource.json.JsonValue; +/** + * This API provides a way to reliably upload larger files to Box by chunking them into a sequence of parts. + * When using this APIinstead of the single file upload API, a request failure means a client only needs to + * retry upload of a single part instead of the entire file. Parts can also be uploaded in parallel allowing + * for potential performance improvement. + */ @BoxResourceType("upload-session") public class BoxFileUploadSession extends BoxResource { @@ -30,12 +36,20 @@ public class BoxFileUploadSession extends BoxResource { private Info sessionInfo; + /** + * Constructs a BoxFileUploadSession for a file with a given ID. + * @param api the API connection to be used by the upload session. + * @param id the ID of the upload session. + */ BoxFileUploadSession(BoxAPIConnection api, String id) { super(api, id); } + /** + * Model contains the upload session information. + */ public class Info extends BoxResource.Info { - private BoxAPIConnection api; + private Date sessionExpiresAt; private String uploadSessionId; private Endpoints sessionEndpoints; @@ -43,23 +57,6 @@ public class Info extends BoxResource.Info { private int totalParts; private int partsProcessed; - /** - * Constructs an empty Info object. - */ - public Info() { - super(); - BoxFileUploadSession.this.sessionInfo = this; - } - - /** - * Constructs an Info object by parsing information from a JSON string. - * @param json the JSON string to parse. - */ - public Info(String json) { - super(json); - BoxFileUploadSession.this.sessionInfo = this; - } - /** * Constructs an Info object using an already parsed JSON object. * @param jsonObject the parsed JSON object. @@ -69,42 +66,63 @@ public Info(String json) { BoxFileUploadSession.this.sessionInfo = this; } - private BoxAPIConnection getAPI() { - return this.api; - } - - private void setAPI(BoxAPIConnection api) { - this.api = api; - } - + /** + * Returns the BoxFileUploadSession isntance to which this object belongs to. + * @return the instance of upload session. + */ public BoxFileUploadSession getResource() { return BoxFileUploadSession.this; } + /** + * Returns the total parts of the file that is uploaded in the upload session. + * @return the total number of parts. + */ public int getTotalParts() { return this.totalParts; } + /** + * Returns the parts that are processed so for. + * @return the number of the processed parts. + */ public int getPartsProcessed() { return this.partsProcessed; } + /** + * Returns the date and time at which the upload session expires. + * @return the date and time in UTC format. + */ public Date getSessionExpiresAt() { return this.sessionExpiresAt; } + /** + * Returns the upload session id. + * @return the id string. + */ public String getUploadSessionId() { return this.uploadSessionId; } + /** + * Returns the session endpoints that can be called for this upload session. + * @return the Endpoints instance. + */ public Endpoints getSessionEndpoints() { return this.sessionEndpoints; } + /** + * Returns the size of the each part. Only the last part of the file can be lessor than this value. + * @return the part size. + */ public long getPartSize() { return this.partSize; } + @Override protected void parseJSONMember(JsonObject.Member member) { String memberName = member.getName(); @@ -130,6 +148,9 @@ protected void parseJSONMember(JsonObject.Member member) { } } + /** + * Represents the end points specific to an upload session. + */ public class Endpoints extends BoxJSONObject { private URL listPartsEndpoint; private URL commitEndpoint; @@ -137,26 +158,50 @@ public class Endpoints extends BoxJSONObject { private URL statusEndpoint; private URL abortEndpoint; + /** + * Constructs an Endpoints object using an already parsed JSON object. + * @param jsonObject the parsed JSON object. + */ Endpoints(JsonObject jsonObject) { super(jsonObject); } + /** + * Returns the list parts end point. + * @return the url of the list parts end point. + */ public URL getListPartsEndpoint() { return this.listPartsEndpoint; } + /** + * Returns the commit end point. + * @return the url of the commit end point. + */ public URL getCommitEndpoint() { return this.commitEndpoint; } + /** + * Returns the upload part end point. + * @return the url of the upload part end point. + */ public URL getUploadPartEndpoint() { return this.uploadPartEndpoint; } + /** + * Returns the upload session status end point. + * @return the url of the session end point. + */ public URL getStatusEndpoint() { return this.statusEndpoint; } + /** + * Returns the abort upload session end point. + * @return the url of the abort end point. + */ public URL getAbortEndpoint() { return this.abortEndpoint; } @@ -184,8 +229,18 @@ protected void parseJSONMember(JsonObject.Member member) { } } + /** + * Uploads a chunk of a file to an open upload session. + * @param partId a unique 8 character hex number that identifies the part. + * @param stream the stream that is used to read the chunck using the offset and part size. + * @param offset the byte position where the chunk begins in the file. + * @param partSize the part size returned as part of the upload session instance creation. + * Only the last chunk can have a lesser value. + * @param totalSizeOfFile The total size of the file being uploaded. + * @return the part instance that contains the part id, offset and part size. + */ public BoxFileUploadSessionPart uploadPart(String partId, InputStream stream, long offset, long partSize, - long totalSizeOfFile) throws IOException, NoSuchAlgorithmException { + long totalSizeOfFile) { URL uploadPartURL = this.sessionInfo.getSessionEndpoints().getUploadPartEndpoint(); @@ -193,15 +248,30 @@ public BoxFileUploadSessionPart uploadPart(String partId, InputStream stream, lo request.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_OCTET_STREAM); request.addHeader(HttpHeaders.X_BOX_PART_ID, partId); + //Read the partSize bytes from the stream byte[] bytes = new byte[(int) partSize]; - stream.read(bytes); + try { + stream.read(bytes); + } catch (IOException ioe) { + throw new BoxAPIException("Reading data from stream failed.", ioe); + } + + MessageDigest digestInstance = null; + try { + digestInstance = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1); + } catch (NoSuchAlgorithmException ae) { + throw new BoxAPIException("Digest algorithm not found", ae); + } - byte[] digestBytes = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1).digest(bytes); + //Creates the digest using SHA1 algorithm. Then encodes the bytes using Base64. + byte[] digestBytes = digestInstance.digest(bytes); String digest = Base64.encode(digestBytes); request.addHeader(HttpHeaders.DIGEST, DIGEST_HEADER_PREFIX_SHA + digest); + //Content-Range: bytes offset-part/totalSize request.addHeader(HttpHeaders.CONTENT_RANGE, "bytes " + offset + "-" + (offset + partSize - 1) + "/" + totalSizeOfFile); + //Creates the body request.setBody(new ByteArrayInputStream(bytes)); request.send(); @@ -213,6 +283,12 @@ public BoxFileUploadSessionPart uploadPart(String partId, InputStream stream, lo return part; } + /** + * Returns a list of all parts that have been uploaded to an upload session. + * @param marker paging marker for the list of parts. + * @param limit maximum number of parts to return. + * @return the list of parts. + */ public BoxFileUploadSessionPartList listParts(int marker, int limit) { URL listPartsURL = this.sessionInfo.getSessionEndpoints().getListPartsEndpoint(); URLTemplate template = new URLTemplate(listPartsURL.toString()); @@ -221,6 +297,7 @@ public BoxFileUploadSessionPartList listParts(int marker, int limit) { .appendParam(MARKER_QUERY_STRING, marker) .appendParam(LIMIT_QUERY_STRING, limit) .toString(); + //Template is initalized with the full URL. So empty string for the path. URL url = template.buildWithQuery("", queryString); BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, HttpMethod.GET); @@ -230,6 +307,15 @@ public BoxFileUploadSessionPartList listParts(int marker, int limit) { return new BoxFileUploadSessionPartList(jsonObject); } + /** + * Commit an upload session after all parts have been uploaded, creating the new file or the version. + * @param digest the base64-encoded SHA-1 hash of the file being uploaded. + * @param parts the list of uploaded parts to be committed. + * @param attributes the key value pairs of attributes from the file instance. + * @param ifMatch ensures that your app only alters files/folders on Box if you have the current version. + * @param ifNoneMatch ensure that it retrieve unnecessary data if the most current version of file is on-hand. + * @return the created file instance. + */ public BoxFile.Info commit(String digest, List parts, Map attributes, String ifMatch, String ifNoneMatch) { @@ -246,10 +332,12 @@ public BoxFile.Info commit(String digest, List parts, request.addHeader(HttpHeaders.IF_NONE_MATCH, ifNoneMatch); } + //Creates the body of the request String body = this.getCommitBody(parts, attributes); request.setBody(body); BoxAPIResponse response = request.send(); + //Retry the commit operation after the given number of seconds if the HTTP response code is 202. if (response.getResponseCode() == 202) { String retryInterval = response.getHeaderField("retry-after"); if (retryInterval != null) { @@ -264,6 +352,7 @@ public BoxFile.Info commit(String digest, List parts, } if (response instanceof BoxJSONResponse) { + //Create the file instance from the response return this.getFile((BoxJSONResponse) response); } else { throw new BoxAPIException("Commit response content type is not application/json. The response code : " @@ -271,6 +360,9 @@ public BoxFile.Info commit(String digest, List parts, } } + /* + * Creates the file isntance from the JSON body of the response. + */ private BoxFile.Info getFile(BoxJSONResponse response) { JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); @@ -282,6 +374,9 @@ private BoxFile.Info getFile(BoxJSONResponse response) { return file.new Info(fileObj); } + /* + * Creates the JSON body for the commit request. + */ private String getCommitBody(List parts, Map attributes) { JsonObject jsonObject = new JsonObject(); @@ -307,6 +402,11 @@ private String getCommitBody(List parts, Map getParts() { return this.parts; } + /** + * Returns the paging marker for the list of parts. + * @return the paging marker. + */ public int getMarker() { return this.marker; } + @Override protected void parseJSONMember(JsonObject.Member member) { String memberName = member.getName(); JsonValue value = member.getValue(); @@ -45,6 +55,9 @@ protected void parseJSONMember(JsonObject.Member member) { } } + /* + * Creates List of parts from the JSON array + */ private List getParts(JsonArray partsArray) { List parts = new ArrayList(); for (JsonValue value: partsArray) { diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index cf49647d3..68b7a9467 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -729,6 +729,13 @@ public void deleteMetadata(String templateName, String scope) { response.disconnect(); } + /** + * Creates an upload session to create a new file in chunks. + * This will first verify that the file can be created and then open a session for uploading pieces of the file. + * @param fileName the name of the file to be created + * @param fileSize the size of the file that will be uploaded + * @return the created upload session instance + */ public BoxFileUploadSession.Info createUploadSession(String fileName, long fileSize) { URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL()); @@ -749,7 +756,14 @@ public BoxFileUploadSession.Info createUploadSession(String fileName, long fileS return session.new Info(jsonObject); } - public BoxFile.Info uploadLargeFile(InputStream inputStream, String fileName, long fileSize) throws Exception { + /** + * Creates a new file. + * @param inputStream the stream instance that contains the data. + * @param fileName the name of the file to be created. + * @param fileSize the size of the file that will be uploaded. + * @return the created file instance. + */ + public BoxFile.Info uploadLargeFile(InputStream inputStream, String fileName, long fileSize) { URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL()); return LargeFileUpload.upload(this.getAPI(), this.getID(), inputStream, url, fileName, fileSize); diff --git a/src/main/java/com/box/sdk/LargeFileUpload.java b/src/main/java/com/box/sdk/LargeFileUpload.java index 8024cf749..44fd9bce7 100644 --- a/src/main/java/com/box/sdk/LargeFileUpload.java +++ b/src/main/java/com/box/sdk/LargeFileUpload.java @@ -1,9 +1,11 @@ package com.box.sdk; +import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.security.DigestInputStream; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -12,7 +14,7 @@ import com.eclipsesource.json.JsonObject; /** - * + * Utility class for uploading large files. */ public final class LargeFileUpload { @@ -25,18 +27,37 @@ public final class LargeFileUpload { private LargeFileUpload() { } + /** + * Uploads a new large file. + * @param boxApi the API connection to be used by the upload session. + * @param folderId the id of the folder in which the file will be uploaded. + * @param stream the input stream that feeds the content of the file. + * @param url the upload session URL. + * @param fileName the name of the file to be created. + * @param fileSize the total size of the file. + * @return the created file instance. + */ static BoxFile.Info upload(BoxAPIConnection boxApi, String folderId, InputStream stream, URL url, - String fileName, long fileSize) throws Exception { + String fileName, long fileSize) { + //Create a upload session BoxFileUploadSession.Info session = createUploadSession(boxApi, folderId, url, fileName, fileSize); - MessageDigest digest = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1); + MessageDigest digest = null; + try { + digest = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1); + } catch (NoSuchAlgorithmException ae) { + throw new BoxAPIException("Digest algorithm not found", ae); + } + + //Upload parts using the upload session List parts = uploadParts(session, stream, fileSize, digest); + //Creates the file hash byte[] digestBytes = digest.digest(); String digestStr = Base64.encode(digestBytes); - BoxFileUploadSessionPartList list = session.getResource().listParts(0, 1000); + //Commit the upload session. If there is a failure, abort the commit. try { return session.getResource().commit(digestStr, parts, null, null, null); } finally { @@ -49,6 +70,7 @@ private static BoxFileUploadSession.Info createUploadSession(BoxAPIConnection bo BoxJSONRequest request = new BoxJSONRequest(boxApi, url, HttpMethod.POST); + //Create the JSON body of the request JsonObject body = new JsonObject(); body.add("folder_id", folderId); body.add("file_name", fileName); @@ -64,15 +86,34 @@ private static BoxFileUploadSession.Info createUploadSession(BoxAPIConnection bo return session.new Info(jsonObject); } - static BoxFile.Info upload(BoxAPIConnection boxApi, InputStream stream, URL url, long fileSize) throws Exception { + /** + * Creates a new version of a large file. + * @param boxApi the API connection to be used by the upload session. + * @param stream the input stream that feeds the content of the file. + * @param url the upload session URL. + * @param fileSize the total size of the file. + * @return the file instance that also contains the version information. + */ + static BoxFile.Info upload(BoxAPIConnection boxApi, InputStream stream, URL url, long fileSize) { + + //creates a upload session BoxFileUploadSession.Info session = createUploadSession(boxApi, url, fileSize); - MessageDigest digest = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1); + MessageDigest digest = null; + try { + digest = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1); + } catch (NoSuchAlgorithmException ae) { + throw new BoxAPIException("Digest algorithm not found", ae); + } + + //Upload parts using the upload session List parts = uploadParts(session, stream, fileSize, digest); + //Creates the file hash byte[] digestBytes = digest.digest(); String digestStr = Base64.encode(digestBytes); + //Commit the upload session. If there is a failure, abort the commit. BoxFileUploadSessionPartList list = session.getResource().listParts(0, 1000); try { return session.getResource().commit(digestStr, parts, null, null, null); @@ -83,6 +124,8 @@ static BoxFile.Info upload(BoxAPIConnection boxApi, InputStream stream, URL url, private static BoxFileUploadSession.Info createUploadSession(BoxAPIConnection boxApi, URL url, long fileSize) { BoxJSONRequest request = new BoxJSONRequest(boxApi, url, HttpMethod.POST); + + //Creates the body of the request JsonObject body = new JsonObject(); body.add("file_size", fileSize); request.setBody(body.toString()); @@ -96,8 +139,11 @@ private static BoxFileUploadSession.Info createUploadSession(BoxAPIConnection bo return session.new Info(jsonObject); } + /* + * Upload parts of the file. The part size is retrieved from the upload session. + */ private static List uploadParts(BoxFileUploadSession.Info session, InputStream stream, - long fileSize, MessageDigest digest) throws Exception { + long fileSize, MessageDigest digest) { DigestInputStream dis = new DigestInputStream(stream, digest); List parts = new ArrayList(); @@ -107,13 +153,16 @@ private static List uploadParts(BoxFileUploadSession.I long processed = 0; while (processed < fileSize) { long diff = fileSize - processed; + //The size last part of the file can be lesser than the part size. if (diff < partSize) { partSize = diff; } + //Upload a part BoxFileUploadSessionPart part = uploadPart(session.getResource(), dis, offset, partSize, fileSize); parts.add(part); + //Increase the offset and proceesed bytes to calculate the Content-Range header. processed += partSize; offset += partSize; } @@ -121,11 +170,15 @@ private static List uploadParts(BoxFileUploadSession.I return parts; } + /* + * Uploads the part of the file. + */ private static BoxFileUploadSessionPart uploadPart(BoxFileUploadSession session, InputStream stream, long offset, - long partSize, long fileSize) throws Exception { + long partSize, long fileSize) { String partId = generateHex(); + //Retries the upload part 3 times in case of failure. for (int i = 0; i < 3; i++) { try { return session.uploadPart(partId, stream, offset, partSize, fileSize); @@ -136,9 +189,13 @@ private static BoxFileUploadSessionPart uploadPart(BoxFileUploadSession session, } } - return null; + throw new BoxAPIException("Upload part failed for offset: " + offset + " range: " + partSize); } + /** + * Generates a 8 character random hex value. + * @return the hex string. + */ public static String generateHex() { String hex = ""; while (hex.length() != 8) { @@ -149,4 +206,34 @@ public static String generateHex() { return hex; } + + /** + * Generates the Base64 encoded SHA-1 hash for content available in the stream. + * It can be used to calculate the hash of a file. + * @param stream the input stream of the file or data. + * @return the Base64 encoded hash string. + */ + public static String generateDigest(InputStream stream) { + MessageDigest digest = null; + try { + digest = MessageDigest.getInstance(DIGEST_ALGORITHM_SHA1); + } catch (NoSuchAlgorithmException ae) { + throw new BoxAPIException("Digest algorithm not found", ae); + } + + //Calcuate the digest using the stream. + DigestInputStream dis = new DigestInputStream(stream, digest); + try { + int value = dis.read(); + while (value != -1) { + value = dis.read(); + } + } catch (IOException ioe) { + throw new BoxAPIException("Reading the stream failed.", ioe); + } + + //Get the calculated digest for the stream + byte[] digestBytes = digest.digest(); + return Base64.encode(digestBytes); + } } diff --git a/src/main/java/com/box/sdk/http/ContentType.java b/src/main/java/com/box/sdk/http/ContentType.java index c16312e1d..1106cc26b 100644 --- a/src/main/java/com/box/sdk/http/ContentType.java +++ b/src/main/java/com/box/sdk/http/ContentType.java @@ -1,11 +1,18 @@ package com.box.sdk.http; /** - * + * HTTP Content-Type constants. */ public final class ContentType { + /** + * It is used when the HTTP request content type is application/json. + */ public static final String APPLICATION_JSON = "application/json"; + + /** + * It is used when the HTTP request content type is application/octet-stream. + */ public static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; //Prevents instantiation diff --git a/src/main/java/com/box/sdk/http/HttpHeaders.java b/src/main/java/com/box/sdk/http/HttpHeaders.java index f9b151afa..aac67afeb 100644 --- a/src/main/java/com/box/sdk/http/HttpHeaders.java +++ b/src/main/java/com/box/sdk/http/HttpHeaders.java @@ -1,16 +1,43 @@ package com.box.sdk.http; /** - * + * HTTP header Key constants. */ public final class HttpHeaders { + /** + * HTTP header key Content-Length. + */ public static final String CONTENT_LENGTH = "Content-Length"; - public static final String CONTENT_RANGE = "Content-Range"; + + /** + * HTTP header key Content-Type. + */ public static final String CONTENT_TYPE = "Content-Type"; + + /** + * HTTP header key Content-Range. + */ + public static final String CONTENT_RANGE = "Content-Range"; + + /** + * HTTP header key DIgest. + */ public static final String DIGEST = "Digest"; + + /** + * HTTP header key If-Match. + */ public static final String IF_MATCH = "If-Match"; + + /** + * HTTP header key If-None-Match. + */ public static final String IF_NONE_MATCH = "If-None-Match"; + + /** + * HTTP header key X-Box-Part-Id. + */ public static final String X_BOX_PART_ID = "X-Box-Part-Id"; //Prevents instantiation diff --git a/src/main/java/com/box/sdk/http/HttpMethod.java b/src/main/java/com/box/sdk/http/HttpMethod.java index 062b94f54..8c08ff287 100644 --- a/src/main/java/com/box/sdk/http/HttpMethod.java +++ b/src/main/java/com/box/sdk/http/HttpMethod.java @@ -1,9 +1,46 @@ package com.box.sdk.http; /** - * + * HTTP method constants. */ public enum HttpMethod { + /** + * HTTP GET method. + */ + GET, - GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; + /** + * HTTP HEAD method. + */ + HEAD, + + /** + * HTTP POST method. + */ + POST, + + /** + * HTTP PUT method. + */ + PUT, + + /** + * HTTP PATCH method. + */ + PATCH, + + /** + * HTTP DELETE method. + */ + DELETE, + + /** + * HTTP OPTIONS method. + */ + OPTIONS, + + /** + * HTTP TRACE method. + */ + TRACE; } From fee47f5c09feee98ad899233be904698ae7783c0 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Thu, 2 Mar 2017 13:31:05 -0800 Subject: [PATCH 102/119] coding style changes --- src/test/java/com/box/sdk/BoxFileTest.java | 28 ++++++++++---------- src/test/java/com/box/sdk/BoxFolderTest.java | 6 ++--- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index c6a0620fd..3c7da109a 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -985,19 +985,19 @@ public void uploadSessionCommitFlowSuccess() throws Exception { long fileSize = file.length(); //Create the session - BoxFileUploadSession.Info session = createFileUploadSession(rootFolder, fileName, fileSize); + BoxFileUploadSession.Info session = this.createFileUploadSession(rootFolder, fileName, fileSize); //Create the parts - MessageDigest fileDigest = uploadParts(uploadedFile, session, fileSize); + MessageDigest fileDigest = this.uploadParts(uploadedFile, session, fileSize); //List the session parts - List parts = listUploadSessionParts(session.getResource()); + List parts = this.listUploadSessionParts(session.getResource()); byte[] digestBytes = fileDigest.digest(); String digest = Base64.encode(digestBytes); //Verify the delete session - uploadedFile = commitSession(session.getResource(), digest, parts); + uploadedFile = this.commitSession(session.getResource(), digest, parts); } finally { if (uploadedFile != null) { uploadedFile.delete(); @@ -1029,24 +1029,24 @@ public void uploadSessionVersionCommitFlowSuccess() throws Exception { BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); BoxFolder rootFolder = BoxFolder.getRootFolder(api); - BoxFile.Info imageFileInfo = createImageFile(rootFolder); + BoxFile.Info imageFileInfo = this.createImageFile(rootFolder); BoxFile uploadedFile = imageFileInfo.getResource(); try { //Create the session - BoxFileUploadSession.Info session = createFileUploadSession(uploadedFile, imageFileInfo.getSize()); + BoxFileUploadSession.Info session = this.createFileUploadSession(uploadedFile, imageFileInfo.getSize()); //Create the parts - MessageDigest fileDigest = uploadParts(uploadedFile, session, imageFileInfo.getSize()); + MessageDigest fileDigest = this.uploadParts(uploadedFile, session, imageFileInfo.getSize()); //List the session parts - List parts = listUploadSessionParts(session.getResource()); + List parts = this.listUploadSessionParts(session.getResource()); byte[] digestBytes = fileDigest.digest(); String digest = Base64.encode(digestBytes); //Verify the delete session - uploadedFile = commitSession(session.getResource(), digest, parts); + uploadedFile = this.commitSession(session.getResource(), digest, parts); } finally { uploadedFile.delete(); } @@ -1085,7 +1085,7 @@ private MessageDigest uploadParts(BoxFile uploadedFile, BoxFileUploadSession.Inf byte[] bytes = null; long processed = 0; boolean canBreak = false; - while(true) { + while (true) { long min = session.getPartSize(); long diff = fileSize - processed; if (diff < min) { @@ -1093,7 +1093,7 @@ private MessageDigest uploadParts(BoxFile uploadedFile, BoxFileUploadSession.Inf canBreak = true; } - session.getResource().uploadPart(generateHex(), dis, offset, min, fileSize); + session.getResource().uploadPart(this.generateHex(), dis, offset, min, fileSize); offset = offset + session.getPartSize(); processed += min; @@ -1158,10 +1158,10 @@ public void uploadSessionAbortFlowSuccess() throws Exception { Assert.assertNotNull(endpoints.getAbortEndpoint()); //Verify the status of the session - getUploadSessionStatus(session.getResource()); + this.getUploadSessionStatus(session.getResource()); //Verify the delete session - abortUploadSession(session.getResource()); + this.abortUploadSession(session.getResource()); } finally { uploadedFile.delete(); } @@ -1199,7 +1199,7 @@ private void abortUploadSession(BoxFileUploadSession session) { //If the session is aborted, this line should not be executed. Assert.assertFalse("Upload session is not deleted", true); - } catch(BoxAPIException apiEx) { + } catch (BoxAPIException apiEx) { Assert.assertEquals(apiEx.getResponseCode(), 404); } } diff --git a/src/test/java/com/box/sdk/BoxFolderTest.java b/src/test/java/com/box/sdk/BoxFolderTest.java index 5da3f0e12..1a94b1a42 100644 --- a/src/test/java/com/box/sdk/BoxFolderTest.java +++ b/src/test/java/com/box/sdk/BoxFolderTest.java @@ -1093,10 +1093,10 @@ public void uploadSessionAbortFlowSuccess() throws Exception { Assert.assertNotNull(endpoints.getAbortEndpoint()); //Verify the status of the session - getUploadSessionStatus(session.getResource()); + this.getUploadSessionStatus(session.getResource()); //Verify the delete session - abortUploadSession(session.getResource()); + this.abortUploadSession(session.getResource()); } finally { uploadedFile.delete(); } @@ -1118,7 +1118,7 @@ private void abortUploadSession(BoxFileUploadSession session) { //If the session is aborted, this line should not be executed. Assert.assertFalse("Upload session is not deleted", true); - } catch(BoxAPIException apiEx) { + } catch (BoxAPIException apiEx) { Assert.assertEquals(apiEx.getResponseCode(), 404); } } From c6e7c045cce3b75f593276ab6f1ce41496a6f51a Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Thu, 2 Mar 2017 17:29:22 -0800 Subject: [PATCH 103/119] BoxFile.Info variable added in files.md --- doc/files.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/files.md b/doc/files.md index 6b7113c74..67d997f7e 100644 --- a/doc/files.md +++ b/doc/files.md @@ -111,7 +111,7 @@ Files are uploaded to a folder by calling the ```java BoxFolder rootFolder = BoxFolder.getRootFolder(api); FileInputStream stream = new FileInputStream("My File.txt"); -rootFolder.uploadFile(stream, "My File.txt"); +BoxFile.Info newFileInfo = rootFolder.uploadFile(stream, "My File.txt"); stream.close(); ``` @@ -123,7 +123,7 @@ Upload progress can be tracked by providing the size of the file and a ```java BoxFolder rootFolder = BoxFolder.getRootFolder(api); FileInputStream stream = new FileInputStream("My File.txt"); -rootFolder.uploadFile(stream, "My File.txt", 1024, new ProgressListener() { +BoxFile.Info newFileInfo = rootFolder.uploadFile(stream, "My File.txt", 1024, new ProgressListener() { public void onProgressChanged(long numBytes, long totalBytes) { double percentComplete = numBytes / totalBytes; } @@ -371,4 +371,4 @@ for (Metadata metadata : metadataList) { } ``` -[get-all-metadata]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFile.html#getAllMetadata(java.lang.String...) \ No newline at end of file +[get-all-metadata]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFile.html#getAllMetadata(java.lang.String...) From 2af99ce9d05c2efda48f0a3ff5280cb7603bf2d3 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Sun, 5 Mar 2017 21:53:08 -0800 Subject: [PATCH 104/119] getAuthorizationURL implementation --- .../java/com/box/sdk/BoxAPIConnection.java | 36 +++++++++++++++++++ .../com/box/sdk/BoxAPIConnectionTest.java | 35 ++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/src/main/java/com/box/sdk/BoxAPIConnection.java b/src/main/java/com/box/sdk/BoxAPIConnection.java index ed6639b51..243a21a3f 100644 --- a/src/main/java/com/box/sdk/BoxAPIConnection.java +++ b/src/main/java/com/box/sdk/BoxAPIConnection.java @@ -2,6 +2,7 @@ import java.net.MalformedURLException; import java.net.Proxy; +import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.List; @@ -23,6 +24,7 @@ public class BoxAPIConnection { */ public static final int DEFAULT_MAX_ATTEMPTS = 3; + private static final String AUTHORIZATION_URL = "https://account.box.com/api/oauth2/authorize"; private static final String TOKEN_URL_STRING = "https://api.box.com/oauth2/token"; private static final String DEFAULT_BASE_URL = "https://api.box.com/2.0/"; private static final String DEFAULT_BASE_UPLOAD_URL = "https://upload.box.com/api/2.0/"; @@ -125,6 +127,40 @@ public static BoxAPIConnection restore(String clientID, String clientSecret, Str return api; } + /** + * Return the authorization URL which is used to perform the authorization_code based OAuth2 flow. + * @param clientID the client ID to use with the connection. + * @param redirectUri the URL to which Box redirects the browser when authentication completes. + * @param state the text string that you choose. + * Box sends the same string to your redirect URL when authentication is complete. + * @param scopes this optional parameter identifies the Box scopes available + * to the application once it's authenticated. + * @return the authorization URL + */ + public static URL getAuthorizationURL(String clientID, URI redirectUri, String state, List scopes) { + URLTemplate template = new URLTemplate(AUTHORIZATION_URL); + QueryStringBuilder queryBuilder = new QueryStringBuilder().appendParam("client_id", clientID) + .appendParam("response_type", "code") + .appendParam("redirect_uri", redirectUri.toString()) + .appendParam("state", state); + + if (scopes != null && !scopes.isEmpty()) { + StringBuilder builder = new StringBuilder(); + int size = scopes.size() - 1; + int i = 0; + while (i < size) { + builder.append(scopes.get(i)); + builder.append(","); + i++; + } + builder.append(scopes.get(i)); + + queryBuilder.appendParam("scope", builder.toString()); + } + + return template.buildWithQuery("", queryBuilder.toString()); + } + /** * Authenticates the API connection by obtaining access and refresh tokens using the auth code that was obtained * from the first half of OAuth. diff --git a/src/test/java/com/box/sdk/BoxAPIConnectionTest.java b/src/test/java/com/box/sdk/BoxAPIConnectionTest.java index c37cd6ce9..a4ea5bb18 100644 --- a/src/test/java/com/box/sdk/BoxAPIConnectionTest.java +++ b/src/test/java/com/box/sdk/BoxAPIConnectionTest.java @@ -1,7 +1,11 @@ package com.box.sdk; import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertFalse; @@ -11,6 +15,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -119,6 +124,36 @@ public String getJSON() { assertFalse(restoredAPI.needsRefresh()); } + @Test + @Category(UnitTest.class) + public void getAuthorizetionURLSuccess() throws Exception { + List scopes = new ArrayList(); + scopes.add("root_readwrite"); + + URL authURL = BoxAPIConnection.getAuthorizationURL("wncmz88sacf5oyaxf502dybcruqbzzy0", + new URI("http://localhost:3000"), "test", scopes); + + System.out.println("Response: " + authURL.toString()); + + Assert.assertTrue(authURL.toString().startsWith("https://account.box.com/api/oauth2/authorize")); + + StringTokenizer tokenizer = new StringTokenizer(authURL.getQuery(), "&"); + while (tokenizer.hasMoreTokens()) { + String token = tokenizer.nextToken(); + if (token.startsWith("client_id")) { + Assert.assertEquals(token, "client_id=wncmz88sacf5oyaxf502dybcruqbzzy0"); + } else if (token.startsWith("response_type")) { + Assert.assertEquals(token, "response_type=code"); + } else if (token.startsWith("redirect_uri")) { + Assert.assertEquals(token, "redirect_uri=http%3A%2F%2Flocalhost%3A3000"); + } else if (token.startsWith("state")) { + Assert.assertEquals(token, "state=test"); + } else if (token.startsWith("scope")) { + Assert.assertEquals(token, "scope=root_readwrite"); + } + } + } + @Test @Category(IntegrationTest.class) public void requestIsSentNormallyWhenInterceptorReturnsNullResponse() throws MalformedURLException { From 36ccad7e687808d6bd3b4cdcc0573ae815f4140a Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Mon, 6 Mar 2017 13:48:57 -0800 Subject: [PATCH 105/119] Scopes are delimited by spaces --- src/main/java/com/box/sdk/BoxAPIConnection.java | 2 +- src/test/java/com/box/sdk/BoxAPIConnectionTest.java | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxAPIConnection.java b/src/main/java/com/box/sdk/BoxAPIConnection.java index 243a21a3f..61b0c8002 100644 --- a/src/main/java/com/box/sdk/BoxAPIConnection.java +++ b/src/main/java/com/box/sdk/BoxAPIConnection.java @@ -150,7 +150,7 @@ public static URL getAuthorizationURL(String clientID, URI redirectUri, String s int i = 0; while (i < size) { builder.append(scopes.get(i)); - builder.append(","); + builder.append(" "); i++; } builder.append(scopes.get(i)); diff --git a/src/test/java/com/box/sdk/BoxAPIConnectionTest.java b/src/test/java/com/box/sdk/BoxAPIConnectionTest.java index a4ea5bb18..895ac561a 100644 --- a/src/test/java/com/box/sdk/BoxAPIConnectionTest.java +++ b/src/test/java/com/box/sdk/BoxAPIConnectionTest.java @@ -129,12 +129,11 @@ public String getJSON() { public void getAuthorizetionURLSuccess() throws Exception { List scopes = new ArrayList(); scopes.add("root_readwrite"); + scopes.add("manage_groups"); URL authURL = BoxAPIConnection.getAuthorizationURL("wncmz88sacf5oyaxf502dybcruqbzzy0", new URI("http://localhost:3000"), "test", scopes); - System.out.println("Response: " + authURL.toString()); - Assert.assertTrue(authURL.toString().startsWith("https://account.box.com/api/oauth2/authorize")); StringTokenizer tokenizer = new StringTokenizer(authURL.getQuery(), "&"); @@ -149,7 +148,7 @@ public void getAuthorizetionURLSuccess() throws Exception { } else if (token.startsWith("state")) { Assert.assertEquals(token, "state=test"); } else if (token.startsWith("scope")) { - Assert.assertEquals(token, "scope=root_readwrite"); + Assert.assertEquals(token, "scope=root_readwrite+manage_groups"); } } } From df522d673b227e2e9d1c0201c0bdbfcdadb3266b Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Tue, 7 Mar 2017 11:29:33 -0800 Subject: [PATCH 106/119] Files examples updates --- doc/files.md | 229 ++++++++++++++++++ .../com/box/sdk/BoxFileUploadSession.java | 4 +- .../java/com/box/sdk/LargeFileUpload.java | 5 +- src/test/java/com/box/sdk/BoxFileTest.java | 6 +- src/test/java/com/box/sdk/BoxFolderTest.java | 6 +- 5 files changed, 239 insertions(+), 11 deletions(-) diff --git a/doc/files.md b/doc/files.md index 67d997f7e..44fcb8908 100644 --- a/doc/files.md +++ b/doc/files.md @@ -134,6 +134,119 @@ stream.close(); [upload]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFolder.html#uploadFile(java.io.InputStream,%20java.lang.String) [upload2]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFolder.html#uploadFile(java.io.InputStream,%20java.lang.String,%20long,%20com.box.sdk.ProgressListener) +Upload a large File in chunks +-------------------------------------- + +An upload session can be created with the [`createUploadSession(fileName, fileSize)`][create-upload-session] method to +upload a large file in chunks. + +```java +//Create the upload session +BoxFile file = new BoxFile(api, "id"); +BoxFileUploadSession.Info sessionInfo = file.createUploadSession("My_Large_File.txt", fileSize); + +//Get the session resource from the session info +BoxFileUploadSession session = sessionInfo.getResource(); + +//Create the Message Digest for the whole file +MessageDigest digest = null; +try { + digest = MessageDigest.getInstance("SHA1"); +} catch (NoSuchAlgorithmException ae) { + throw new BoxAPIException("Digest algorithm not found", ae); +} +``` +Once the upload session is created, using that session the large file can be uploaded in chuncks with the +[`uploadPart(partId, stream, offset, partSize, totalSizeOfFile)`][upload-part] method of the session instance. +If there is a failure in uploading any of the parts, +the failed part can be uploaded again without affecting the other parts. + +```java +//Reading a large file +FileInputStream fis = new FileInputStream("My_Large_File.txt"); +//Create the digest input stream to calculate the digest for the whole file. +DigestInputStream dis = new DigestInputStream(fis, digest); + +List parts = new ArrayList(); + +//Get the part size. Each uploaded part should match the part size returned as part of the upload session. +//The last part of the file can be less than part size if the remaining bytes of the last part is less than +//the given part size +long partSize = sessionInfo.getPartSize(); +//Start byte of the part +long offset = 0; +//Overall of bytes processed so far +long processed = 0; +while (processed < fileSize) { + long diff = fileSize - processed; + //The size last part of the file can be lesser than the part size. + if (diff < partSize) { + partSize = diff; + } + + //Generate a unique partId + String partId = LargeFileUpload.generateHex(); + //Upload a part. It can be uploaded asynchorously + BoxFileUploadSessionPart part = session.uploadPart(partId, dis, offset, partSize, fileSize); + parts.add(part); + + //Increase the offset and proceesed bytes to calculate the Content-Range header. + processed += partSize; + offset += partSize; +} +``` + +At any point in time, the list of parts that are being uploaded successfully can be retrivied with the +[`listParts(marker, limit)`][list-parts] method of the session instance. + +```java +//The following snippet retrives first 1000 parts that are uploaded. Both can be modified based on the needs. +BoxFileUploadSessionPartList partList = session.listParts(0, 1000); +List parts = partList.getParts(); +``` +Once all the parts are uploaded successfully. the upload sessiion can be commited with the +[`commit(digest, parts, attributes, ifMatch, ifNoneMatch)`][upload-session-commit] method. + +```java +//Creates the file hash +byte[] digestBytes = digest.digest(); +//Base64 encoding of the hash +String digestStr = Base64.encode(digestBytes); + +//Commit the upload session. If there is a failure, abort the commit. +BoxFile.Info fileInfo = session.commit(digestStr, parts, null, null, null); +``` + +The upload session can be aborted at any time with the [`abort()`][upload-session-abort] method of the session instance. + +```java +session.abort(); +``` + +The upload session status can be retrived at any time with the [`getstatus()`][upload-session-status] method. +This call will update the parts processed and other information in the session info instance. +```java +BoxFileUploadSession.Info updatedSessionInfo = session.getStatus(); +``` + +[create-upload-session]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFolder.html#createUploadSession(java.lang.String,%20long) + +Create a large File +------------------- + +A large file can be uploaded with the [`uploadLargeFile(InputStream, fileName, fileSize)`][upload-large-file] method. + +```java +File myFile = new File("My Large_File.txt"); +FileInputStream stream = new FileInputStream(myFile); + +BoxFolder rootFolder = BoxFolder.getRootFolder(api); +BoxFile.Info fileInfo = rootFolder.uploadLargeFile(inputStream, "My_Large_File.txt", myFile.length()); +``` + +[upload-large-file]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFolder.html#uploadLargeFile(java.io.InputStream,%20java.lang.String,%20long) + + Copy a File ----------- @@ -239,6 +352,122 @@ firstVersion.delete(); [delete-version]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFileVersion.html#delete() +Create a versioning of a large File by uploading its content in chunks +---------------------------------------------------------------------- + +An upload session can be created with the [`createUploadSession(fileSize)`][create-upload-session-version] +method to upload new version of a large file in chunks. + +```java +BoxFile file = new BoxFile(api, "id"); +BoxFileUploadSession.Info session = file.createUploadSession(fileSize); + +//Get the session resource from the session info +BoxFileUploadSession session = sessionInfo.getResource(); + +//Create the Message Digest for the whole file +MessageDigest digest = null; +try { + digest = MessageDigest.getInstance("SHA1"); +} catch (NoSuchAlgorithmException ae) { + throw new BoxAPIException("Digest algorithm not found", ae); +} +``` +Once the upload session is created, the large file can be uploaded in chuncks with the +[`uploadPart(partId, stream, offset, partSize, totalSizeOfFile)`][upload-part] method of the session instance. +If there is a failure in uploading any of the parts, the failed part can be uploaded again without +affecting the other parts. + +```java +//Reading a large file +FileInputStream fis = new FileInputStream("My_Large_File.txt"); +//Create the digest input stream to calculate the digest for the whole file. +DigestInputStream dis = new DigestInputStream(fis, digest); + +List parts = new ArrayList(); + +//Get the part size. Each uploaded part should match the part size returned as part of the upload session. +//The last part of the file can be less than part size if the remaining bytes of the last part is less than +//the given part size +long partSize = sessionInfo.getPartSize(); +//Start byte of the part +long offset = 0; +//Overall of bytes processed so far +long processed = 0; +while (processed < fileSize) { + long diff = fileSize - processed; + //The size last part of the file can be lesser than the part size. + if (diff < partSize) { + partSize = diff; + } + + //Generate a unique partId + String partId = LargeFileUpload.generateHex(); + //Upload a part. It can be uploaded asynchorously + BoxFileUploadSessionPart part = session.uploadPart(partId, dis, offset, partSize, fileSize); + parts.add(part); + + //Increase the offset and proceesed bytes to calculate the Content-Range header. + processed += partSize; + offset += partSize; +} +``` +At any point in time, the list of parts that are being uploaded successfully can be retrivied with the +[`listParts(marker, limit)`][list-parts] method of the session instance. + +```java +//The following snippet retrives first 1000 parts that are uploaded. Both can be modified based on the needs. +BoxFileUploadSessionPartList partList = session.listParts(0, 1000); +List parts = partList.getParts(); +``` +Once all the parts are uploaded successfully. the upload sessiion can be commited with the +[`commit(digest, parts, attributes, ifMatch, ifNoneMatch)`][upload-session-commit] method. + +```java +//Creates the file hash +byte[] digestBytes = digest.digest(); +//Base64 encoding of the hash +String digestStr = Base64.encode(digestBytes); + +//Commit the upload session. If there is a failure, abort the commit. +BoxFile.Info fileInfo = session.commit(digestStr, parts, null, null, null); +``` + +The upload session can be aborted at any time with the [`abort()`][upload-session-abort] method of the session instance. + +```java +session.abort(); +``` + +The upload session status can be retrived at any time with the [`getstatus()`][upload-session-status] method. +This call will update the parts processed and other information in the session info instance. +```java +BoxFileUploadSession.Info sessionInfo = session.getStatus(); +``` + +[create-upload-session-version]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFile.html#uploadVersion(long) +[upload-part]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFileUploadSession.html#uploadPart(java.lang.String,%20java.io.InputStream,%20long,%20long,%20long) +[list-parts]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFileUploadSession.html#listParts(int,%20int) +[upload-session-commit]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFileUploadSession.html#commit(java.lang.String,%20java.util.List,%20java.util.Map,%20java.lang.String,%20java.lang.String) +[upload-session-abort]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFileUploadSession.html#abort() +[upload-session-status]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFileUploadSession.html#getStatus() + +Create new version of a large File +---------------------------------- + +New versions of a large file can be uploaded with the +[`uploadLargeFile(InputStream, fileSize)`][upload-large-file-version] method. + +```java +File myFile = new File("My File.txt"); +FileInputStream stream = new FileInputStream(myFile); + +BoxFile file = new BoxFile(api, "id"); +BoxFile.Info versionedFileInfo = file.uploadLargeFile(inputStream, myFile.length()); +``` + +[upload-large-file-version]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFile.html#uploadLargeFile(java.io.InputStream,%20long) + Lock a File ----------- diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index 995c5268f..11bb2f621 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -407,7 +407,7 @@ private String getCommitBody(List parts, Map Date: Tue, 7 Mar 2017 13:58:03 -0800 Subject: [PATCH 107/119] large file upload test file input modified --- src/test/java/com/box/sdk/BoxFileTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index a2683a309..71835d737 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -1214,7 +1214,10 @@ private static byte[] readAllBytes(String fileName) throws IOException { @Test @Category(IntegrationTest.class) public void uploadLargeFile() throws Exception { - File file = new File("/Users/kshanmugasundaram/Downloads/tenmb"); + String fileName = "Tamme-Lauri_tamm_suvepäeval.jpg"; + URL fileURL = this.getClass().getResource("/sample-files/" + fileName); + String filePath = URLDecoder.decode(fileURL.getFile(), "utf-8"); + File file = new File(filePath); FileInputStream stream = new FileInputStream(file); BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); From 4765b1cf65fa922f64a2f4e929f207fe3589c192 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Tue, 7 Mar 2017 16:17:56 -0800 Subject: [PATCH 108/119] list parts marker fix --- .../java/com/box/sdk/BoxFileUploadSession.java | 12 +++++++----- src/test/java/com/box/sdk/BoxFileTest.java | 18 ++++++++++++------ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFileUploadSession.java b/src/main/java/com/box/sdk/BoxFileUploadSession.java index 11bb2f621..08f61edce 100644 --- a/src/main/java/com/box/sdk/BoxFileUploadSession.java +++ b/src/main/java/com/box/sdk/BoxFileUploadSession.java @@ -289,14 +289,16 @@ public BoxFileUploadSessionPart uploadPart(String partId, InputStream stream, lo * @param limit maximum number of parts to return. * @return the list of parts. */ - public BoxFileUploadSessionPartList listParts(int marker, int limit) { + public BoxFileUploadSessionPartList listParts(String marker, int limit) { URL listPartsURL = this.sessionInfo.getSessionEndpoints().getListPartsEndpoint(); URLTemplate template = new URLTemplate(listPartsURL.toString()); - String queryString = new QueryStringBuilder() - .appendParam(MARKER_QUERY_STRING, marker) - .appendParam(LIMIT_QUERY_STRING, limit) - .toString(); + QueryStringBuilder builder = new QueryStringBuilder(); + if (marker != null) { + builder.appendParam(MARKER_QUERY_STRING, marker); + } + String queryString = builder.appendParam(LIMIT_QUERY_STRING, limit).toString(); + //Template is initalized with the full URL. So empty string for the path. URL url = template.buildWithQuery("", queryString); diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index 71835d737..29b96cd77 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -1134,14 +1134,20 @@ private BoxFile.Info createImageFile(BoxFolder folder) throws IOException { @Test @Category(IntegrationTest.class) public void uploadSessionAbortFlowSuccess() throws Exception { - BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); - BoxFolder rootFolder = BoxFolder.getRootFolder(api); + String fileName = "Tamme-Lauri_tamm_suvepäeval.jpg"; + URL fileURL = this.getClass().getResource("/sample-files/" + fileName); + String filePath = URLDecoder.decode(fileURL.getFile(), "utf-8"); + File file = new File(filePath); + long fileSize = file.length(); - String fileName = "[setCollectionsWithInfoSucceeds] Test File.txt"; - String fileContent = "Test file"; - byte[] fileBytes = fileContent.getBytes(StandardCharsets.UTF_8); + FileInputStream stream = new FileInputStream(file); + byte[] fileBytes = new byte[(int) file.length()]; + stream.read(fileBytes); InputStream uploadStream = new ByteArrayInputStream(fileBytes); + + BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); + BoxFolder rootFolder = BoxFolder.getRootFolder(api); BoxFile uploadedFile = rootFolder.uploadFile(uploadStream, fileName).getResource(); try { BoxFileUploadSession.Info session = uploadedFile.createUploadSession(fileBytes.length); @@ -1168,7 +1174,7 @@ public void uploadSessionAbortFlowSuccess() throws Exception { } private List listUploadSessionParts(BoxFileUploadSession session) { - BoxFileUploadSessionPartList list = session.listParts(0, 10); + BoxFileUploadSessionPartList list = session.listParts(null, 10); List parts = list.getParts(); Assert.assertEquals(parts.size(), 3); From 627522783fad7762f0340b79184fd3b6bb1787c7 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Tue, 7 Mar 2017 16:23:58 -0800 Subject: [PATCH 109/119] Abort flow implementation --- src/test/java/com/box/sdk/BoxFolderTest.java | 58 +++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/src/test/java/com/box/sdk/BoxFolderTest.java b/src/test/java/com/box/sdk/BoxFolderTest.java index c8a8e78d9..1df2ef634 100644 --- a/src/test/java/com/box/sdk/BoxFolderTest.java +++ b/src/test/java/com/box/sdk/BoxFolderTest.java @@ -1,9 +1,12 @@ package com.box.sdk; import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; +import java.net.URLDecoder; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Collection; @@ -1072,34 +1075,37 @@ public void uploadSessionAbortFlowSuccess() throws Exception { BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken()); BoxFolder rootFolder = BoxFolder.getRootFolder(api); - String fileName = "[setCollectionsWithInfoSucceeds] Test File.txt"; - String fileContent = "Test file"; - byte[] fileBytes = fileContent.getBytes(StandardCharsets.UTF_8); + String fileName = "Tamme-Lauri_tamm_suvepäeval.jpg"; + URL fileURL = this.getClass().getResource("/sample-files/" + fileName); + String filePath = URLDecoder.decode(fileURL.getFile(), "utf-8"); + File file = new File(filePath); + long fileSize = file.length(); + FileInputStream stream = new FileInputStream(file); + + byte[] fileBytes = new byte[(int) file.length()]; + stream.read(fileBytes); InputStream uploadStream = new ByteArrayInputStream(fileBytes); - BoxFile uploadedFile = rootFolder.uploadFile(uploadStream, fileName).getResource(); - try { - BoxFileUploadSession.Info session = uploadedFile.createUploadSession(fileBytes.length); - Assert.assertNotNull(session.getUploadSessionId()); - Assert.assertNotNull(session.getSessionExpiresAt()); - Assert.assertNotNull(session.getPartSize()); - - BoxFileUploadSession.Endpoints endpoints = session.getSessionEndpoints(); - Assert.assertNotNull(endpoints); - Assert.assertNotNull(endpoints.getUploadPartEndpoint()); - Assert.assertNotNull(endpoints.getStatusEndpoint()); - Assert.assertNotNull(endpoints.getListPartsEndpoint()); - Assert.assertNotNull(endpoints.getCommitEndpoint()); - Assert.assertNotNull(endpoints.getAbortEndpoint()); - - //Verify the status of the session - this.getUploadSessionStatus(session.getResource()); - - //Verify the delete session - this.abortUploadSession(session.getResource()); - } finally { - uploadedFile.delete(); - } + + BoxFileUploadSession.Info session = rootFolder.createUploadSession( + "Tamme-Lauri_tamm_suvepäeval.jpg", fileBytes.length); + Assert.assertNotNull(session.getUploadSessionId()); + Assert.assertNotNull(session.getSessionExpiresAt()); + Assert.assertNotNull(session.getPartSize()); + + BoxFileUploadSession.Endpoints endpoints = session.getSessionEndpoints(); + Assert.assertNotNull(endpoints); + Assert.assertNotNull(endpoints.getUploadPartEndpoint()); + Assert.assertNotNull(endpoints.getStatusEndpoint()); + Assert.assertNotNull(endpoints.getListPartsEndpoint()); + Assert.assertNotNull(endpoints.getCommitEndpoint()); + Assert.assertNotNull(endpoints.getAbortEndpoint()); + + //Verify the status of the session + this.getUploadSessionStatus(session.getResource()); + + //Verify the delete session + this.abortUploadSession(session.getResource()); } private void getUploadSessionStatus(BoxFileUploadSession session) { From 987b7d282e662569868c9a17a2a4eaebb83ab4f9 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Mon, 27 Mar 2017 09:52:19 -0700 Subject: [PATCH 110/119] Update BoxFile.java --- src/main/java/com/box/sdk/BoxFile.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 6dd19ca8a..fd0d1c044 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -70,8 +70,6 @@ public enum ThumbnailFileType { "files/upload-session/%s/status"); private static final URLTemplate ABORT_UPLOAD_SESSION_URL_TEMPLATE = new URLTemplate("files/upload-session/%s"); - private static final int BUFFER_SIZE = 8192; - private static final URLTemplate ADD_COLLABORATION_URL = new URLTemplate("collaborations"); private static final URLTemplate GET_ALL_FILE_COLLABORATIONS_URL = new URLTemplate("files/%s/collaborations"); From 943fd4738e7b9903f04e4dc1aa5561de93c5749d Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Tue, 28 Mar 2017 13:56:06 -0700 Subject: [PATCH 111/119] notify and canViewPath parameters are added --- src/main/java/com/box/sdk/BoxFile.java | 24 +++++++++++++++---- .../com/box/sdk/BoxCollaborationTest.java | 5 ++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index fd0d1c044..842ba2eff 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -1203,7 +1203,8 @@ String toJSONValue() { } } - private BoxCollaboration.Info collaborate(JsonObject accessibleByField, BoxCollaboration.Role role) { + private BoxCollaboration.Info collaborate(JsonObject accessibleByField, BoxCollaboration.Role role, + Boolean notify, Boolean canViewPath) { BoxAPIConnection api = this.getAPI(); URL url = ADD_COLLABORATION_URL.build(api.getBaseURL()); @@ -1215,8 +1216,15 @@ private BoxCollaboration.Info collaborate(JsonObject accessibleByField, BoxColla requestJSON.add("item", itemField); requestJSON.add("accessible_by", accessibleByField); requestJSON.add("role", role.toJSONString()); + if (canViewPath != null) { + requestJSON.add("can_view_path", canViewPath.booleanValue()); + } BoxJSONRequest request = new BoxJSONRequest(api, url, "POST"); + if (notify != null) { + request.addHeader("notify", notify.toString()); + } + request.setBody(requestJSON.toString()); BoxJSONResponse response = (BoxJSONResponse) request.send(); JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); @@ -1231,9 +1239,12 @@ private BoxCollaboration.Info collaborate(JsonObject accessibleByField, BoxColla * * @param collaborator the collaborator to add. * @param role the role of the collaborator. + * @param notify determines if the user (or all the users in the group) will receive email notifications. + * @param canViewPath whether view path collaboration feature is enabled or not. * @return info about the new collaboration. */ - public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role) { + public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role, + Boolean notify, Boolean canViewPath) { JsonObject accessibleByField = new JsonObject(); accessibleByField.add("id", collaborator.getID()); @@ -1244,7 +1255,7 @@ public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollab } else { throw new IllegalArgumentException("The given collaborator is of an unknown type."); } - return this.collaborate(accessibleByField, role); + return this.collaborate(accessibleByField, role, notify, canViewPath); } @@ -1254,14 +1265,17 @@ public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollab * * @param email the email address of the collaborator to add. * @param role the role of the collaborator. + * @param notify determines if the user (or all the users in the group) will receive email notifications. + * @param canViewPath whether view path collaboration feature is enabled or not. * @return info about the new collaboration. */ - public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role) { + public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role, + Boolean notify, Boolean canViewPath) { JsonObject accessibleByField = new JsonObject(); accessibleByField.add("login", email); accessibleByField.add("type", "user"); - return this.collaborate(accessibleByField, role); + return this.collaborate(accessibleByField, role, notify, canViewPath); } /** diff --git a/src/test/java/com/box/sdk/BoxCollaborationTest.java b/src/test/java/com/box/sdk/BoxCollaborationTest.java index ffff8f2fc..d4daac25e 100644 --- a/src/test/java/com/box/sdk/BoxCollaborationTest.java +++ b/src/test/java/com/box/sdk/BoxCollaborationTest.java @@ -84,7 +84,7 @@ public void singleFileCollabSucceeds() { BoxCollaboration.Role originalRole = BoxCollaboration.Role.VIEWER; BoxCollaboration.Role newRole = BoxCollaboration.Role.EDITOR; - BoxCollaboration.Info collabInfo = uploadedFile.collaborate(collaboratorLogin, originalRole); + BoxCollaboration.Info collabInfo = uploadedFile.collaborate(collaboratorLogin, originalRole, true, false); collabsMap.put(collabInfo.getID(), collabInfo); @@ -102,7 +102,8 @@ public void singleFileCollabSucceeds() { assertThat(remoteInfo.getCreatedBy().getID(), is(collabInfo.getCreatedBy().getID())); - BoxCollaboration.Info collab2Info = uploadedFile.collaborate("davidsmaynard@gmail.com", originalRole); + BoxCollaboration.Info collab2Info = uploadedFile.collaborate("davidsmaynard@gmail.com", originalRole, + true, false); collabsMap.put(collab2Info.getID(), collab2Info); From 39dce32ee21c9beec0428c19667c2f3abb3cbc46 Mon Sep 17 00:00:00 2001 From: Karthik Shanmugasundaram Date: Wed, 29 Mar 2017 11:41:14 -0700 Subject: [PATCH 112/119] upload session url change --- .../java/com/box/sdk/BoxAPIConnection.java | 19 ------------------- src/main/java/com/box/sdk/BoxFile.java | 4 ++-- src/main/java/com/box/sdk/BoxFolder.java | 4 ++-- 3 files changed, 4 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxAPIConnection.java b/src/main/java/com/box/sdk/BoxAPIConnection.java index 61b0c8002..1912a5df3 100644 --- a/src/main/java/com/box/sdk/BoxAPIConnection.java +++ b/src/main/java/com/box/sdk/BoxAPIConnection.java @@ -28,7 +28,6 @@ public class BoxAPIConnection { private static final String TOKEN_URL_STRING = "https://api.box.com/oauth2/token"; private static final String DEFAULT_BASE_URL = "https://api.box.com/2.0/"; private static final String DEFAULT_BASE_UPLOAD_URL = "https://upload.box.com/api/2.0/"; - private static final String DEFAULT_BASE_UPLOAD_SESSION_URL = "https://upload.app.box.com/api/2.0/"; /** * The amount of buffer time, in milliseconds, to use when determining if an access token should be refreshed. For @@ -55,7 +54,6 @@ public class BoxAPIConnection { private String tokenURL; private String baseURL; private String baseUploadURL; - private String baseUploadSessionURL; private boolean autoRefresh; private int maxRequestAttempts; private List listeners; @@ -84,7 +82,6 @@ public BoxAPIConnection(String clientID, String clientSecret, String accessToken this.tokenURL = TOKEN_URL_STRING; this.baseURL = DEFAULT_BASE_URL; this.baseUploadURL = DEFAULT_BASE_UPLOAD_URL; - this.baseUploadSessionURL = DEFAULT_BASE_UPLOAD_SESSION_URL; this.autoRefresh = true; this.maxRequestAttempts = DEFAULT_MAX_ATTEMPTS; this.refreshLock = new ReentrantReadWriteLock(); @@ -276,22 +273,6 @@ public void setBaseUploadURL(String baseUploadURL) { this.baseUploadURL = baseUploadURL; } - /** - * Gets the base upload session URL that's used when performing supercharged uploads to Box. - * @return the base upload session URL. - */ - public String getBaseUploadSessionURL() { - return this.baseUploadSessionURL; - } - - /** - * Sets the base upload URL to be used when performing supercharged uploads to Box. - * @param baseUploadSessionURL a base upload URL. - */ - public void setBaseUploadSessionURL(String baseUploadSessionURL) { - this.baseUploadSessionURL = baseUploadSessionURL; - } - /** * Gets the user agent that's used when sending requests to the Box API. * @return the user agent. diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 842ba2eff..38c38d2bd 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -905,7 +905,7 @@ public BoxFile.Info setCollections(BoxCollection... collections) { * @return the created upload session instance. */ public BoxFileUploadSession.Info createUploadSession(long fileSize) { - URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), this.getID()); + URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID()); BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); request.addHeader("Content-Type", "application/json"); @@ -929,7 +929,7 @@ public BoxFileUploadSession.Info createUploadSession(long fileSize) { * @return the created file instance. */ public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize) { - URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL(), this.getID()); + URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID()); return LargeFileUpload.upload(this.getAPI(), inputStream, url, fileSize); } diff --git a/src/main/java/com/box/sdk/BoxFolder.java b/src/main/java/com/box/sdk/BoxFolder.java index cdaf33677..d04ca30a3 100644 --- a/src/main/java/com/box/sdk/BoxFolder.java +++ b/src/main/java/com/box/sdk/BoxFolder.java @@ -816,7 +816,7 @@ public void deleteMetadata(String templateName, String scope) { */ public BoxFileUploadSession.Info createUploadSession(String fileName, long fileSize) { - URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL()); + URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL()); BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); JsonObject body = new JsonObject(); @@ -842,7 +842,7 @@ public BoxFileUploadSession.Info createUploadSession(String fileName, long fileS * @return the created file instance. */ public BoxFile.Info uploadLargeFile(InputStream inputStream, String fileName, long fileSize) { - URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadSessionURL()); + URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL()); return LargeFileUpload.upload(this.getAPI(), this.getID(), inputStream, url, fileName, fileSize); } From 5f155ef6ad3b7ea7f803011a29485a6d89926c1e Mon Sep 17 00:00:00 2001 From: Tabs Date: Mon, 28 Nov 2016 13:22:27 -0500 Subject: [PATCH 113/119] support user events in event log, minor bug fixes for enterprise log inconsistencies --- src/main/java/com/box/sdk/EventLog.java | 61 +++++++++++++++++++++---- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/box/sdk/EventLog.java b/src/main/java/com/box/sdk/EventLog.java index 618867f4c..089135fdd 100644 --- a/src/main/java/com/box/sdk/EventLog.java +++ b/src/main/java/com/box/sdk/EventLog.java @@ -22,20 +22,28 @@ public class EventLog implements Iterable { private static final int ENTERPRISE_LIMIT = 500; private static final URLTemplate ENTERPRISE_EVENT_URL_TEMPLATE = new URLTemplate("events?stream_type=admin_logs&" + "limit=" + ENTERPRISE_LIMIT); + private static final int USER_LIMIT = 800; + private static final URLTemplate USER_EVENT_URL_TEMPLATE = new URLTemplate("events?limit=" + USER_LIMIT + "&stream_position=%s"); + public static final long STREAM_POSITION_NOW = -1; private final int chunkSize; private final int limit; - private final String nextStreamPosition; - private final String streamPosition; + private final long nextStreamPosition; + private final long streamPosition; private final Set set; private Date startDate; private Date endDate; - EventLog(BoxAPIConnection api, JsonObject json, String streamPosition, int limit) { + EventLog(BoxAPIConnection api, JsonObject json, long streamPosition, int limit) { this.streamPosition = streamPosition; this.limit = limit; - this.nextStreamPosition = json.get("next_stream_position").asString(); + JsonValue position = json.get("next_stream_position"); + if(position.isString()) { + this.nextStreamPosition = Long.valueOf(position.asString()); + } else { + this.nextStreamPosition = position.asLong(); + } this.chunkSize = json.get("chunk_size").asInt(); this.set = new LinkedHashSet(this.chunkSize); @@ -54,7 +62,7 @@ public class EventLog implements Iterable { * @return a log of all the events that met the given criteria. */ public static EventLog getEnterpriseEvents(BoxAPIConnection api, Date after, Date before, BoxEvent.Type... types) { - return getEnterpriseEvents(api, null, after, before, types); + return getEnterpriseEvents(api, STREAM_POSITION_NOW, after, before, types); } /** @@ -67,12 +75,12 @@ public static EventLog getEnterpriseEvents(BoxAPIConnection api, Date after, Dat * @param types an optional list of event types to filter by. * @return a log of all the events that met the given criteria. */ - public static EventLog getEnterpriseEvents(BoxAPIConnection api, String position, Date after, Date before, + public static EventLog getEnterpriseEvents(BoxAPIConnection api, long position, Date after, Date before, BoxEvent.Type... types) { URL url = ENTERPRISE_EVENT_URL_TEMPLATE.build(api.getBaseURL()); - if (position != null || types.length > 0 || after != null + if (types.length > 0 || after != null || before != null) { QueryStringBuilder queryBuilder = new QueryStringBuilder(url.getQuery()); @@ -86,7 +94,7 @@ public static EventLog getEnterpriseEvents(BoxAPIConnection api, String position BoxDateFormat.format(before)); } - if (position != null) { + if (position != STREAM_POSITION_NOW) { queryBuilder.appendParam("stream_position", position); } @@ -178,7 +186,7 @@ public int getLimit() { * * @return the starting position within the event stream. */ - public String getStreamPosition() { + public long getStreamPosition() { return this.streamPosition; } @@ -190,7 +198,7 @@ public String getStreamPosition() { * * @return the next position within the event stream. */ - public String getNextStreamPosition() { + public long getNextStreamPosition() { return this.nextStreamPosition; } @@ -219,4 +227,37 @@ public int getChunkSize() { public int getSize() { return this.set.size(); } + + public static EventLog getUserEvents(BoxAPIConnection api, long position, BoxEvent.Type... types) { + if (position == STREAM_POSITION_NOW) { + BoxAPIRequest request = new BoxAPIRequest(api, USER_EVENT_URL_TEMPLATE.build(api.getBaseURL(), "now"), "GET"); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); + position = jsonObject.get("next_stream_position").asLong(); + } + + URL url = USER_EVENT_URL_TEMPLATE.build(api.getBaseURL(), position); + + QueryStringBuilder queryBuilder = new QueryStringBuilder(url.getQuery()); + + StringBuilder filterBuilder = new StringBuilder(); + for (BoxEvent.Type filterType : types) { + filterBuilder.append(filterType.name()); + filterBuilder.append(','); + } + filterBuilder.deleteCharAt(filterBuilder.length() - 1); + queryBuilder.appendParam("event_type", filterBuilder.toString()); + + try { + url = queryBuilder.addToURL(url); + } catch (MalformedURLException e) { + throw new BoxAPIException("Couldn't append a query string to the provided URL."); + } + + BoxAPIRequest request = new BoxAPIRequest(api, url, "GET"); + BoxJSONResponse response = (BoxJSONResponse) request.send(); + JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); + EventLog log = new EventLog(api, responseJSON, position, USER_LIMIT); + return log; + } } From b9b8c44165d8acf5d091dbab2b956a4697948ff6 Mon Sep 17 00:00:00 2001 From: Tabs Date: Tue, 29 Nov 2016 21:54:56 -0500 Subject: [PATCH 114/119] add auth refres into api request handling --- .../java/com/box/sdk/BoxAPIConnection.java | 2 +- src/main/java/com/box/sdk/BoxAPIRequest.java | 8 +++- .../box/sdk/ReloadableBoxAPIConnection.java | 42 +++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/box/sdk/ReloadableBoxAPIConnection.java diff --git a/src/main/java/com/box/sdk/BoxAPIConnection.java b/src/main/java/com/box/sdk/BoxAPIConnection.java index 1912a5df3..40a883899 100644 --- a/src/main/java/com/box/sdk/BoxAPIConnection.java +++ b/src/main/java/com/box/sdk/BoxAPIConnection.java @@ -49,7 +49,7 @@ public class BoxAPIConnection { private String proxyPassword; private String userAgent; - private String accessToken; + protected String accessToken; private String refreshToken; private String tokenURL; private String baseURL; diff --git a/src/main/java/com/box/sdk/BoxAPIRequest.java b/src/main/java/com/box/sdk/BoxAPIRequest.java index 036da1671..7d6d888b1 100644 --- a/src/main/java/com/box/sdk/BoxAPIRequest.java +++ b/src/main/java/com/box/sdk/BoxAPIRequest.java @@ -220,7 +220,11 @@ public BoxAPIResponse send(ProgressListener listener) { try { return this.trySend(listener); } catch (BoxAPIException apiException) { - if (!this.backoffCounter.decrement() || !isResponseRetryable(apiException.getResponseCode())) { + this.backoffCounter.decrement(); + + if (this.backoffCounter.getAttemptsRemaining() > 0 && this.api.canRefresh() && isResponseUnauthorized(apiException.getResponseCode())) { + this.api.refresh(); + } else if (this.backoffCounter.getAttemptsRemaining() < 1 || !isResponseRetryable(apiException.getResponseCode())) { throw apiException; } @@ -527,6 +531,8 @@ private static boolean isResponseRedirect(int responseCode) { return (responseCode == 301 || responseCode == 302); } + private static boolean isResponseUnauthorized(int responseCode) { return responseCode == 401; } + private final class RequestHeader { private final String key; private final String value; diff --git a/src/main/java/com/box/sdk/ReloadableBoxAPIConnection.java b/src/main/java/com/box/sdk/ReloadableBoxAPIConnection.java new file mode 100644 index 000000000..896a61503 --- /dev/null +++ b/src/main/java/com/box/sdk/ReloadableBoxAPIConnection.java @@ -0,0 +1,42 @@ +package com.box.sdk; + +import com.eclipsesource.json.JsonObject; + +import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Represents an authenticated connection to the Box API. + * + *

This class handles storing authentication information, automatic token refresh, and rate-limiting. It can also be + * used to configure the Box API endpoint URL in order to hit a different version of the API. Multiple instances of + * BoxAPIConnection may be created to support multi-user login.

+ */ +public class ReloadableBoxAPIConnection extends BoxAPIConnection { + /** + * Constructs a new ReloadableBoxAPIConnection with an access token that can be refreshed. + * @param clientID the client ID to use when refreshing the access token. + * @param clientSecret the client secret to use when refreshing the access token. + * @param accessToken an initial access token to use for authenticating with the API. + * @param refreshToken an initial refresh token to use when refreshing the access token. + */ + public ReloadableBoxAPIConnection(String clientID, String clientSecret, String accessToken, String refreshToken) { + super(clientID, clientSecret, accessToken, refreshToken); + } + + /** + * Gets an access token that can be used to authenticate an API request. This method will automatically refresh the + * access token if it has expired since the last call to getAccessToken(). + * @return a valid access token that can be used to authenticate an API request. + */ + public String getAccessToken() { return this.accessToken; } + + public boolean needsRefresh() { + return false; + } +} From d44ae1f72797c4f26b6f08e18fe1d804d5488ed6 Mon Sep 17 00:00:00 2001 From: Tabs Date: Fri, 23 Dec 2016 13:25:52 -0500 Subject: [PATCH 115/119] user event log do not take a types parameter --- src/main/java/com/box/sdk/EventLog.java | 10 +-------- .../box/sdk/ReloadableBoxAPIConnection.java | 21 ++++--------------- 2 files changed, 5 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/box/sdk/EventLog.java b/src/main/java/com/box/sdk/EventLog.java index 089135fdd..5bd53ea29 100644 --- a/src/main/java/com/box/sdk/EventLog.java +++ b/src/main/java/com/box/sdk/EventLog.java @@ -228,7 +228,7 @@ public int getSize() { return this.set.size(); } - public static EventLog getUserEvents(BoxAPIConnection api, long position, BoxEvent.Type... types) { + public static EventLog getUserEvents(BoxAPIConnection api, long position) { if (position == STREAM_POSITION_NOW) { BoxAPIRequest request = new BoxAPIRequest(api, USER_EVENT_URL_TEMPLATE.build(api.getBaseURL(), "now"), "GET"); BoxJSONResponse response = (BoxJSONResponse) request.send(); @@ -240,14 +240,6 @@ public static EventLog getUserEvents(BoxAPIConnection api, long position, BoxEve QueryStringBuilder queryBuilder = new QueryStringBuilder(url.getQuery()); - StringBuilder filterBuilder = new StringBuilder(); - for (BoxEvent.Type filterType : types) { - filterBuilder.append(filterType.name()); - filterBuilder.append(','); - } - filterBuilder.deleteCharAt(filterBuilder.length() - 1); - queryBuilder.appendParam("event_type", filterBuilder.toString()); - try { url = queryBuilder.addToURL(url); } catch (MalformedURLException e) { diff --git a/src/main/java/com/box/sdk/ReloadableBoxAPIConnection.java b/src/main/java/com/box/sdk/ReloadableBoxAPIConnection.java index 896a61503..5719d9fbd 100644 --- a/src/main/java/com/box/sdk/ReloadableBoxAPIConnection.java +++ b/src/main/java/com/box/sdk/ReloadableBoxAPIConnection.java @@ -1,19 +1,9 @@ package com.box.sdk; -import com.eclipsesource.json.JsonObject; - -import java.net.MalformedURLException; -import java.net.Proxy; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - /** * Represents an authenticated connection to the Box API. * - *

This class handles storing authentication information, automatic token refresh, and rate-limiting. It can also be + *

This class handles storing authentication information, token refresh, and rate-limiting. It can also be * used to configure the Box API endpoint URL in order to hit a different version of the API. Multiple instances of * BoxAPIConnection may be created to support multi-user login.

*/ @@ -30,13 +20,10 @@ public ReloadableBoxAPIConnection(String clientID, String clientSecret, String a } /** - * Gets an access token that can be used to authenticate an API request. This method will automatically refresh the - * access token if it has expired since the last call to getAccessToken(). - * @return a valid access token that can be used to authenticate an API request. + * Gets an access token that can be used to authenticate an API request. + * @return an access token that can be used to authenticate an API request. */ public String getAccessToken() { return this.accessToken; } - public boolean needsRefresh() { - return false; - } + public boolean needsRefresh() { return false; } } From 8d2827fe79bb8284efb919a68a5bc0dc93cdcbc3 Mon Sep 17 00:00:00 2001 From: Ryan Cooke Date: Wed, 8 Mar 2017 12:03:31 -0500 Subject: [PATCH 116/119] Adds a BoxAPIConnectionListener method for determining whether a BoxAPIConnection can refresh its tokens --- src/main/java/com/box/sdk/BoxAPIConnection.java | 2 +- .../java/com/box/sdk/BoxAPIConnectionListener.java | 13 +++++++++++++ .../com/box/sdk/ReloadableBoxAPIConnection.java | 13 +++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/box/sdk/BoxAPIConnection.java b/src/main/java/com/box/sdk/BoxAPIConnection.java index 40a883899..f09b9f98c 100644 --- a/src/main/java/com/box/sdk/BoxAPIConnection.java +++ b/src/main/java/com/box/sdk/BoxAPIConnection.java @@ -56,7 +56,7 @@ public class BoxAPIConnection { private String baseUploadURL; private boolean autoRefresh; private int maxRequestAttempts; - private List listeners; + protected List listeners; private RequestInterceptor interceptor; /** diff --git a/src/main/java/com/box/sdk/BoxAPIConnectionListener.java b/src/main/java/com/box/sdk/BoxAPIConnectionListener.java index a30395815..c30642ee3 100644 --- a/src/main/java/com/box/sdk/BoxAPIConnectionListener.java +++ b/src/main/java/com/box/sdk/BoxAPIConnectionListener.java @@ -5,6 +5,19 @@ */ public interface BoxAPIConnectionListener { + /** + * Called before the Box API connection refreshes its tokens. + * It can short circuit the refresh if the listener returns false. + * This is useful for testing the availability of a persistence resource such as a database. + * + *

The provided connection is guaranteed to not be auto-refreshed or modified by another listener until this + * method returns.

+ * + * @param api the API connection that was refreshed. + * @return a boolean indicating whether the refresh should proceed + */ + default boolean preRefresh(BoxAPIConnection api) { return true; }; + /** * Called when the Box API connection refreshes its tokens. * diff --git a/src/main/java/com/box/sdk/ReloadableBoxAPIConnection.java b/src/main/java/com/box/sdk/ReloadableBoxAPIConnection.java index 5719d9fbd..276746da4 100644 --- a/src/main/java/com/box/sdk/ReloadableBoxAPIConnection.java +++ b/src/main/java/com/box/sdk/ReloadableBoxAPIConnection.java @@ -23,7 +23,20 @@ public ReloadableBoxAPIConnection(String clientID, String clientSecret, String a * Gets an access token that can be used to authenticate an API request. * @return an access token that can be used to authenticate an API request. */ + @Override public String getAccessToken() { return this.accessToken; } + @Override + public boolean canRefresh() { + if(super.canRefresh()) { + for(BoxAPIConnectionListener listener: listeners) + if (!listener.preRefresh(this)) + return false; + return true; + } + return false; + } + + @Override public boolean needsRefresh() { return false; } } From d9dc6e6af8db6066c76b0bf3adddb43513cc8be6 Mon Sep 17 00:00:00 2001 From: Ryan Cooke Date: Wed, 8 Mar 2017 19:11:21 -0500 Subject: [PATCH 117/119] Don't acquire the refresh lock if can't refresh, and don't raise exceptions when you can notify errors --- src/main/java/com/box/sdk/BoxAPIConnection.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxAPIConnection.java b/src/main/java/com/box/sdk/BoxAPIConnection.java index f09b9f98c..aa879fe42 100644 --- a/src/main/java/com/box/sdk/BoxAPIConnection.java +++ b/src/main/java/com/box/sdk/BoxAPIConnection.java @@ -471,14 +471,14 @@ public boolean needsRefresh() { * @throws IllegalStateException if this connection's access token cannot be refreshed. */ public void refresh() { - this.refreshLock.writeLock().lock(); - if (!this.canRefresh()) { - this.refreshLock.writeLock().unlock(); - throw new IllegalStateException("The BoxAPIConnection cannot be refreshed because it doesn't have a " - + "refresh token."); + BoxAPIException e = new BoxAPIException("The BoxAPIConnection cannot be refreshed because canRefresh check failed."); + this.notifyError(e); + return; } + this.refreshLock.writeLock().lock(); + URL url = null; try { url = new URL(this.tokenURL); @@ -502,7 +502,7 @@ public void refresh() { } catch (BoxAPIException e) { this.notifyError(e); this.refreshLock.writeLock().unlock(); - throw e; + return; } JsonObject jsonObject = JsonObject.readFrom(json); From aeb6191bef851ff44e3507eb2586fabc8de9fce9 Mon Sep 17 00:00:00 2001 From: Ryan Cooke Date: Thu, 9 Mar 2017 20:15:58 -0500 Subject: [PATCH 118/119] canRefresh() happens to be called in a lot of situations, and is not a suitable place to invoke preRefresh --- .../com/box/sdk/ReloadableBoxAPIConnection.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/box/sdk/ReloadableBoxAPIConnection.java b/src/main/java/com/box/sdk/ReloadableBoxAPIConnection.java index 276746da4..3cf869ba6 100644 --- a/src/main/java/com/box/sdk/ReloadableBoxAPIConnection.java +++ b/src/main/java/com/box/sdk/ReloadableBoxAPIConnection.java @@ -27,14 +27,12 @@ public ReloadableBoxAPIConnection(String clientID, String clientSecret, String a public String getAccessToken() { return this.accessToken; } @Override - public boolean canRefresh() { - if(super.canRefresh()) { - for(BoxAPIConnectionListener listener: listeners) - if (!listener.preRefresh(this)) - return false; - return true; - } - return false; + public void refresh() { + for(BoxAPIConnectionListener listener: listeners) + if (!listener.preRefresh(this)) + return; + + super.refresh(); } @Override From 36106b86820d68f027c13bd7009df62d390a5ce1 Mon Sep 17 00:00:00 2001 From: ACE Date: Mon, 17 Apr 2017 15:49:17 -0400 Subject: [PATCH 119/119] Support for new box event --- src/main/java/com/box/sdk/BoxEvent.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/com/box/sdk/BoxEvent.java b/src/main/java/com/box/sdk/BoxEvent.java index c03332710..b772ab869 100644 --- a/src/main/java/com/box/sdk/BoxEvent.java +++ b/src/main/java/com/box/sdk/BoxEvent.java @@ -219,6 +219,11 @@ public enum Type { */ ITEM_PREVIEW, + /** + * A representation of a file was accessed. This may include preview or offline access. + */ + CONTENT_ACCESS, + /** * A file or folder was moved. */