diff --git a/.codegen.json b/.codegen.json index 7ad11b1aa..cf952ebc2 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "04310d4", "specHash": "88cd5aa", "version": "10.16.2" } +{ "engineHash": "bcfc85f", "specHash": "88cd5aa", "version": "10.16.2" } diff --git a/build.gradle b/build.gradle index 7100ca37b..7f549062f 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,7 @@ dependencies { implementation "com.squareup.okhttp3:okhttp:${okhttpVersion}" implementation "com.squareup.okio:okio:${okioVersion}" implementation "org.bitbucket.b_c:jose4j:${jose4jVersion}" + implementation "commons-codec:commons-codec:${commonsCodecVersion}" implementation "org.bouncycastle:bcprov-jdk18on:${bouncyCastleBcprovJdk18onVersion}" implementation "org.bouncycastle:bcpkix-jdk18on:${bouncyCastleBcpkixJdk18onVersion}" testImplementation "org.junit.jupiter:junit-jupiter-api:${junitApiVersion}" diff --git a/docs/chunkeduploads.md b/docs/chunkeduploads.md index 07203bfd6..fc4d1a6ca 100644 --- a/docs/chunkeduploads.md +++ b/docs/chunkeduploads.md @@ -56,7 +56,10 @@ This operation is performed by calling function `createFileUploadSessionForExist See the endpoint docs at [API Reference](https://developer.box.com/reference/post-files-id-upload-sessions/). -*Currently we don't have an example for calling `createFileUploadSessionForExistingFile` in integration tests* + +``` +client.getChunkedUploads().createFileUploadSessionForExistingFile(uploadedFile.getId(), new CreateFileUploadSessionForExistingFileRequestBody(fileSize)) +``` ### Arguments @@ -357,7 +360,10 @@ This operation is performed by calling function `createFileUploadSessionPlanByUr See the endpoint docs at [API Reference](https://developer.box.com/reference/post-files-upload-sessions-id-plan/). -*Currently we don't have an example for calling `createFileUploadSessionPlanByUrl` in integration tests* + +``` +client.getChunkedUploads().createFileUploadSessionPlanByUrl(planUrl, new UploadSessionPlanRequest(parts)) +``` ### Arguments @@ -496,7 +502,7 @@ This operation is performed by calling function `uploadBigFile`. ``` -client.getChunkedUploads().uploadBigFile(fileByteStream, fileName, fileSize, parentFolderId) +client.getChunkedUploads().uploadBigFile(generateByteStreamFromBuffer(fileBuffer), fileName, fileSize, parentFolderId) ``` ### Arguments diff --git a/gradle.properties b/gradle.properties index bc7dd90b8..1bd167fbe 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,6 +6,7 @@ jacksonDatabindVersion=2.21.4 okhttpVersion=4.12.0 okioVersion=3.5.0 jose4jVersion=0.9.6 +commonsCodecVersion=1.22.0 bouncyCastleBcprovJdk18onVersion=1.85 bouncyCastleBcpkixJdk18onVersion=1.85 junitApiVersion=5.10.0 diff --git a/src/main/java/com/box/sdkgen/internal/utils/Hash.java b/src/main/java/com/box/sdkgen/internal/utils/Hash.java index e8a9664c8..42a933a98 100644 --- a/src/main/java/com/box/sdkgen/internal/utils/Hash.java +++ b/src/main/java/com/box/sdkgen/internal/utils/Hash.java @@ -4,6 +4,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; +import org.apache.commons.codec.binary.Hex; public class Hash { @@ -13,7 +14,7 @@ public class Hash { public Hash(HashName algorithm) { this.algorithm = algorithm; try { - this.digest = MessageDigest.getInstance(algorithm.getValue()); + this.digest = MessageDigest.getInstance(getAlgorithmName(algorithm)); } catch (NoSuchAlgorithmException ae) { throw new BoxSDKError("Digest algorithm not found", ae); } @@ -25,10 +26,24 @@ public void updateHash(byte[] data) { public String digestHash(String encoding) { byte[] digestBytes = digest.digest(); + if ("hex".equals(encoding)) { + return Hex.encodeHexString(digestBytes); + } return Base64.getEncoder().encodeToString(digestBytes); } public HashName getAlgorithm() { return algorithm; } + + private static String getAlgorithmName(HashName algorithm) { + switch (algorithm) { + case SHA1: + return "SHA-1"; + case SHA512: + return "SHA-512"; + default: + throw new BoxSDKError("Digest algorithm not supported"); + } + } } diff --git a/src/main/java/com/box/sdkgen/internal/utils/HashName.java b/src/main/java/com/box/sdkgen/internal/utils/HashName.java index ea96f0003..53bad9873 100644 --- a/src/main/java/com/box/sdkgen/internal/utils/HashName.java +++ b/src/main/java/com/box/sdkgen/internal/utils/HashName.java @@ -3,7 +3,8 @@ import com.box.sdkgen.serialization.json.Valuable; public enum HashName implements Valuable { - SHA1("sha1"); + SHA1("sha1"), + SHA512("sha512"); private final String value; diff --git a/src/main/java/com/box/sdkgen/internal/utils/UtilsManager.java b/src/main/java/com/box/sdkgen/internal/utils/UtilsManager.java index 7832c8dcd..a19d4f41a 100644 --- a/src/main/java/com/box/sdkgen/internal/utils/UtilsManager.java +++ b/src/main/java/com/box/sdkgen/internal/utils/UtilsManager.java @@ -15,11 +15,11 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.security.MessageDigest; +import java.security.SecureRandom; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; @@ -42,6 +42,8 @@ import java.util.stream.Collectors; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; import org.jose4j.jws.JsonWebSignature; import org.jose4j.jwt.JwtClaims; import org.jose4j.jwt.NumericDate; @@ -150,7 +152,7 @@ public static String getUuid() { public static byte[] generateByteBuffer(int size) { byte[] bytes = new byte[size]; - Arrays.fill(bytes, (byte) 0); + new SecureRandom().nextBytes(bytes); return bytes; } @@ -306,7 +308,11 @@ public static double random(double min, double max) { } public static String hexToBase64(String hex) { - return Base64.getEncoder().encodeToString(new BigInteger(hex, 16).toByteArray()); + try { + return Base64.getEncoder().encodeToString(Hex.decodeHex(hex)); + } catch (DecoderException e) { + throw new BoxSDKError("Error decoding hex string.", e); + } } public static Iterator iterateChunks( diff --git a/src/test/java/com/box/sdkgen/chunkeduploads/ChunkedUploadsITest.java b/src/test/java/com/box/sdkgen/chunkeduploads/ChunkedUploadsITest.java index 1536763ee..c6c16080f 100644 --- a/src/test/java/com/box/sdkgen/chunkeduploads/ChunkedUploadsITest.java +++ b/src/test/java/com/box/sdkgen/chunkeduploads/ChunkedUploadsITest.java @@ -3,6 +3,7 @@ import static com.box.sdkgen.commons.CommonsManager.getDefaultClient; import static com.box.sdkgen.internal.utils.UtilsManager.bufferLength; import static com.box.sdkgen.internal.utils.UtilsManager.convertToString; +import static com.box.sdkgen.internal.utils.UtilsManager.delayInSeconds; import static com.box.sdkgen.internal.utils.UtilsManager.generateByteStream; import static com.box.sdkgen.internal.utils.UtilsManager.generateByteStreamFromBuffer; import static com.box.sdkgen.internal.utils.UtilsManager.getUuid; @@ -18,6 +19,7 @@ import com.box.sdkgen.managers.chunkeduploads.CreateFileUploadSessionCommitByUrlRequestBody; import com.box.sdkgen.managers.chunkeduploads.CreateFileUploadSessionCommitHeaders; import com.box.sdkgen.managers.chunkeduploads.CreateFileUploadSessionCommitRequestBody; +import com.box.sdkgen.managers.chunkeduploads.CreateFileUploadSessionForExistingFileRequestBody; import com.box.sdkgen.managers.chunkeduploads.CreateFileUploadSessionRequestBody; import com.box.sdkgen.managers.chunkeduploads.UploadFilePartByUrlHeaders; import com.box.sdkgen.managers.chunkeduploads.UploadFilePartHeaders; @@ -25,8 +27,12 @@ import com.box.sdkgen.schemas.files.Files; import com.box.sdkgen.schemas.uploadedpart.UploadedPart; import com.box.sdkgen.schemas.uploadpart.UploadPart; +import com.box.sdkgen.schemas.uploadpartplan.UploadPartPlan; +import com.box.sdkgen.schemas.uploadpartplanhit.UploadPartPlanHit; import com.box.sdkgen.schemas.uploadparts.UploadParts; import com.box.sdkgen.schemas.uploadsession.UploadSession; +import com.box.sdkgen.schemas.uploadsessionplanrequest.UploadSessionPlanRequest; +import com.box.sdkgen.schemas.uploadsessionplanresponse.UploadSessionPlanResponse; import java.io.InputStream; import java.util.Arrays; import java.util.Collections; @@ -126,6 +132,24 @@ public static TestPartAccumulator reducerByUrl(TestPartAccumulator acc, InputStr .build(); } + public static TestPartPlanAccumulator reducerForUploadSessionPlan( + TestPartPlanAccumulator acc, InputStream chunk) { + int lastIndex = acc.getLastIndex(); + List parts = acc.getParts(); + byte[] chunkBuffer = readByteStream(chunk); + Hash hash = new Hash(HashName.SHA512); + hash.updateHash(chunkBuffer); + String sha512 = hash.digestHash("hex"); + int chunkSize = bufferLength(chunkBuffer); + int bytesStart = lastIndex + 1; + int bytesEnd = lastIndex + chunkSize; + UploadPartPlan part = new UploadPartPlan(bytesStart, chunkSize, sha512); + return new TestPartPlanAccumulator( + bytesEnd, + Stream.concat(parts.stream(), Arrays.asList(part).stream()).collect(Collectors.toList()), + acc.getFileSize()); + } + @Test public void testChunkedManualProcessById() { int fileSize = 20 * 1024 * 1024; @@ -221,6 +245,55 @@ public void testChunkedManualProcessByUrl() { client.getChunkedUploads().deleteFileUploadSessionByUrl(abortUrl); } + @Test + public void testUploadSessionPlan() { + int fileSize = 20 * 1024 * 1024; + String fileName = getUuid(); + String parentFolderId = "0"; + InputStream fileContentStream = generateByteStream(fileSize); + byte[] fileBuffer = readByteStream(fileContentStream); + File uploadedFile = + client + .getChunkedUploads() + .uploadBigFile( + generateByteStreamFromBuffer(fileBuffer), fileName, fileSize, parentFolderId); + delayInSeconds(5); + UploadSession uploadSession = + client + .getChunkedUploads() + .createFileUploadSessionForExistingFile( + uploadedFile.getId(), + new CreateFileUploadSessionForExistingFileRequestBody(fileSize)); + String uploadSessionId = uploadSession.getId(); + String planUrl = uploadSession.getSessionEndpoints().getPlan(); + long partSize = uploadSession.getPartSize(); + int totalParts = uploadSession.getTotalParts(); + Iterator chunksIterator = + iterateChunks(generateByteStreamFromBuffer(fileBuffer), partSize, fileSize); + TestPartPlanAccumulator results = + reduceIterator( + chunksIterator, + (TestPartPlanAccumulator acc, InputStream chunk) -> + reducerForUploadSessionPlan(acc, chunk), + new TestPartPlanAccumulator(-1, Collections.emptyList(), fileSize)); + List parts = results.getParts(); + UploadSessionPlanResponse plan = + client + .getChunkedUploads() + .createFileUploadSessionPlanByUrl(planUrl, new UploadSessionPlanRequest(parts)); + assert plan.getUploadSessionId().equals(uploadSessionId); + assert plan.getHits().size() == totalParts; + assert plan.getMisses().size() == 0; + UploadPartPlan firstPart = parts.get(0); + UploadPartPlanHit firstHit = plan.getHits().get(0); + assert firstHit.getOffset() == firstPart.getOffset(); + assert firstHit.getSize() == firstPart.getSize(); + assert firstHit.getSha512().equals(firstPart.getSha512()); + assert !(firstHit.getPartId().equals("")); + client.getChunkedUploads().deleteFileUploadSessionById(uploadSessionId); + client.getFiles().deleteFileById(uploadedFile.getId()); + } + @Test public void testChunkedUploadConvenienceMethod() { int fileSize = 20 * 1024 * 1024; diff --git a/src/test/java/com/box/sdkgen/chunkeduploads/TestPartPlanAccumulator.java b/src/test/java/com/box/sdkgen/chunkeduploads/TestPartPlanAccumulator.java new file mode 100644 index 000000000..2a90e8ed7 --- /dev/null +++ b/src/test/java/com/box/sdkgen/chunkeduploads/TestPartPlanAccumulator.java @@ -0,0 +1,31 @@ +package com.box.sdkgen.chunkeduploads; + +import com.box.sdkgen.schemas.uploadpartplan.UploadPartPlan; +import java.util.List; + +public class TestPartPlanAccumulator { + + public final int lastIndex; + + public final List parts; + + public final long fileSize; + + public TestPartPlanAccumulator(int lastIndex, List parts, long fileSize) { + this.lastIndex = lastIndex; + this.parts = parts; + this.fileSize = fileSize; + } + + public int getLastIndex() { + return lastIndex; + } + + public List getParts() { + return parts; + } + + public long getFileSize() { + return fileSize; + } +}