From a740d6abb38de76d0e4b930546837461780a9cd2 Mon Sep 17 00:00:00 2001 From: Artur Jankowski Date: Thu, 6 Jun 2024 09:49:32 +0200 Subject: [PATCH 1/3] feat: Overload the `getRepresentationContent` method with a `maxRetries` parameter --- doc/files.md | 15 ++++ src/main/java/com/box/sdk/BoxFile.java | 31 ++++++- .../GetFileRepresentation200WithPending.json | 17 ++++ src/test/java/com/box/sdk/BoxFileTest.java | 86 +++++++++++++++++++ 4 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 src/test/Fixtures/BoxFile/GetFileRepresentation200WithPending.json diff --git a/doc/files.md b/doc/files.md index 746ae52b5..b2247500c 100644 --- a/doc/files.md +++ b/doc/files.md @@ -1078,4 +1078,19 @@ BoxFile file = new BoxFile(api, "12345"); file.getRepresentationContent("[png?dimensions=1024x1024]", "1.png", output); ``` +Generating a representation for the selected file is an asynchronous operation and may take some time. +Therefore, by default, the `getRepresentationContent` method periodically checks the status of the generated file and downloads it when it is ready. +With the `maxRetries` parameter in [`getRepresentationContent(String representationHint, String assetPath, OutputStream output, int maxRetries)`][get-rep-content-overloaded], you can define +the number of status checks for the generated file, which will be performed at intervals of 100 ms. + +If this number is exceeded, a `BoxApiException` will be thrown. + +```java +FileOutputStream output = new FileOutputStream("/path/to/file.png"); +BoxFile file = new BoxFile(api, "12345"); +file.getRepresentationContent("[png?dimensions=1024x1024]", "1.png", output, 10); +``` + + [get-rep-content]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFile.html#getRepresentationContent-java.lang.String-java.lang.String-java.io.OutputStream- +[get-rep-content-overloaded]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxFile.html#getRepresentationContent-java.lang.String-java.lang.String-java.io.OutputStream-int- diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 6faeec614..86edc6756 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -505,7 +505,23 @@ public void getRepresentationContent(String representationHint, OutputStream out * @see X-Rep-Hints Header */ public void getRepresentationContent(String representationHint, String assetPath, OutputStream output) { + this.getRepresentationContent(representationHint, assetPath, output, Integer.MAX_VALUE); + } + /** + * Fetches the contents of a file representation with asset path and writes them to the provided output stream. + * + * @param representationHint the X-Rep-Hints query for the representation to fetch. + * @param assetPath the path of the asset for representations containing multiple files. + * @param output the output stream to write the contents to. + * @param maxRetries the maximum number of attempts to call the request for retrieving status information + * indicating whether the representation has been generated and is ready to fetch. + * If the number of attempts is exceeded, the method will throw a BoxApiException. + * @see X-Rep-Hints Header + */ + public void getRepresentationContent( + String representationHint, String assetPath, OutputStream output, int maxRetries + ) { List reps = this.getInfoWithRepresentations(representationHint).getRepresentations(); if (reps.size() < 1) { throw new BoxAPIException("No matching representations found for requested '" + representationHint @@ -523,16 +539,27 @@ public void getRepresentationContent(String representationHint, String assetPath case "none": String repContentURLString = null; - while (repContentURLString == null) { + int attemptNumber = 0; + while (repContentURLString == null && attemptNumber < maxRetries) { repContentURLString = this.pollRepInfo(representation.getInfo().getUrl()); try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } + attemptNumber++; + } + + if (repContentURLString != null) { + this.makeRepresentationContentRequest(repContentURLString, assetPath, output); + } else { + throw new BoxAPIException( + "Representation did non have a success status allowing it to be retrieved after " + + maxRetries + + " attempts" + ); } - this.makeRepresentationContentRequest(repContentURLString, assetPath, output); break; case "error": throw new BoxAPIException("Representation had error status"); diff --git a/src/test/Fixtures/BoxFile/GetFileRepresentation200WithPending.json b/src/test/Fixtures/BoxFile/GetFileRepresentation200WithPending.json new file mode 100644 index 000000000..ec1ade405 --- /dev/null +++ b/src/test/Fixtures/BoxFile/GetFileRepresentation200WithPending.json @@ -0,0 +1,17 @@ +{ + "representation": "jpg", + "properties": { + "dimensions": "32x32", + "paged": false, + "thumb": true + }, + "info": { + "url": "https://localhost:53621/2.0/internal_files/1030335435441/versions/1116437417841/representations/jpg_thumb_32x32" + }, + "status": { + "state": "pending" + }, + "content": { + "url_template": "https://localhost:53621/2.0/internal_files/1030335435441/versions/1116437417841/representations/jpg_thumb_32x32/content/{+asset_path}" + } +} diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index 7f67b0999..abfb3903f 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -15,6 +15,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import com.box.sdk.sharedlink.BoxSharedLinkRequest; import com.eclipsesource.json.Json; @@ -440,6 +441,91 @@ public void testGetThumbnailSucceeds() { assertThat(output.toString(), equalTo("This is a JPG")); } + @Test + public void testGetRepresentationContentThrowsWhenExceedingMaxRetries() { + final String fileID = "12345"; + wireMockRule.stubFor( + WireMock.get(WireMock.urlPathEqualTo("/2.0/files/" + fileID)) + .withQueryParam("fields", WireMock.equalTo("representations")) + .willReturn(WireMock.aResponse() + .withHeader("Content-Type", APPLICATION_JSON) + .withBody(getFixture("BoxFile/GetFileRepresentations200", wireMockRule.httpsPort())) + .withStatus(200)) + ); + wireMockRule.stubFor( + WireMock.get(WireMock.urlPathEqualTo( + "/2.0/internal_files/12345/versions/1116420931563/representations/jpg_thumb_32x32") + ) + .willReturn(WireMock.aResponse() + .withHeader("Content-Type", APPLICATION_JSON) + .withBody(getFixture("BoxFile/GetFileRepresentation200WithPending", wireMockRule.httpsPort())) + .withStatus(200)) + ); + + try { + BoxFile file = new BoxFile(this.api, fileID); + OutputStream output = new ByteArrayOutputStream(); + file.getRepresentationContent("[jpg?dimensions=32x32]", "", output, 5); + fail("getRepresentationContent did not fail with BoxAPIException due to pending status"); + assertThat(output.toString(), equalTo("This is a JPG")); + } catch (BoxAPIException apiException) { + assertEquals( + apiException.getMessage(), + "Representation did non have a success status allowing it to be retrieved after 5 attempts" + ); + } + } + + @Test + public void testGetRepresentationContentSuccess() { + final String fileID = "12345"; + wireMockRule.stubFor( + WireMock.get(WireMock.urlPathEqualTo("/2.0/files/" + fileID)) + .withQueryParam("fields", WireMock.equalTo("representations")) + .willReturn(WireMock.aResponse() + .withHeader("Content-Type", APPLICATION_JSON) + .withBody(getFixture("BoxFile/GetFileRepresentations200", wireMockRule.httpsPort())) + .withStatus(200)) + ); + wireMockRule.stubFor( + WireMock.get(WireMock.urlPathEqualTo( + "/2.0/internal_files/12345/versions/1116420931563/representations/jpg_thumb_32x32") + ) + .inScenario("Get file representation status info") + .willSetStateTo("pending status") + .willReturn(WireMock.aResponse() + .withHeader("Content-Type", APPLICATION_JSON) + .withBody(getFixture("BoxFile/GetFileRepresentation200WithPending", wireMockRule.httpsPort())) + .withStatus(200)) + ); + wireMockRule.stubFor( + WireMock.get(WireMock.urlPathEqualTo( + "/2.0/internal_files/12345/versions/1116420931563/representations/jpg_thumb_32x32") + ) + .inScenario("Get file representation status info") + .whenScenarioStateIs("pending status") + .willReturn(WireMock.aResponse() + .withHeader("Content-Type", APPLICATION_JSON) + .withBody(getFixture("BoxFile/GetFileRepresentation200", wireMockRule.httpsPort())) + .withStatus(200)) + ); + + wireMockRule.stubFor( + WireMock.get(WireMock.urlPathEqualTo( + "/2.0/internal_files/1030335435441/versions/1116437417841/representations/jpg_thumb_32x32/content/" + )) + .willReturn(WireMock.aResponse() + .withHeader("Content-Type", "image/jpg") + .withBody("This is a JPG") + .withStatus(200)) + ); + + BoxFile file = new BoxFile(this.api, fileID); + OutputStream output = new ByteArrayOutputStream(); + file.getRepresentationContent("[jpg?dimensions=32x32]", output); + assertThat(output.toString(), equalTo("This is a JPG")); + } + @Test public void testDeletePreviousFileVersionSucceeds() { final String versionID = "12345"; From c777f91d7a6571eccdab8e5eedc85b72a7e0e9ab Mon Sep 17 00:00:00 2001 From: Artur Jankowski Date: Thu, 6 Jun 2024 14:23:28 +0200 Subject: [PATCH 2/3] fixes --- src/main/java/com/box/sdk/BoxFile.java | 2 +- src/test/java/com/box/sdk/BoxFileTest.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/box/sdk/BoxFile.java b/src/main/java/com/box/sdk/BoxFile.java index 86edc6756..0334abfd2 100644 --- a/src/main/java/com/box/sdk/BoxFile.java +++ b/src/main/java/com/box/sdk/BoxFile.java @@ -554,7 +554,7 @@ public void getRepresentationContent( this.makeRepresentationContentRequest(repContentURLString, assetPath, output); } else { throw new BoxAPIException( - "Representation did non have a success status allowing it to be retrieved after " + "Representation did not have a success status allowing it to be retrieved after " + maxRetries + " attempts" ); diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index abfb3903f..d6eb9c4c2 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -467,7 +467,6 @@ public void testGetRepresentationContentThrowsWhenExceedingMaxRetries() { OutputStream output = new ByteArrayOutputStream(); file.getRepresentationContent("[jpg?dimensions=32x32]", "", output, 5); fail("getRepresentationContent did not fail with BoxAPIException due to pending status"); - assertThat(output.toString(), equalTo("This is a JPG")); } catch (BoxAPIException apiException) { assertEquals( apiException.getMessage(), From 1e0ca9b5acf07d8e825e43a7e52fbd1eeaebc557 Mon Sep 17 00:00:00 2001 From: Artur Jankowski Date: Thu, 6 Jun 2024 14:27:44 +0200 Subject: [PATCH 3/3] fixes 2 --- src/test/java/com/box/sdk/BoxFileTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/box/sdk/BoxFileTest.java b/src/test/java/com/box/sdk/BoxFileTest.java index d6eb9c4c2..0d17c09b0 100644 --- a/src/test/java/com/box/sdk/BoxFileTest.java +++ b/src/test/java/com/box/sdk/BoxFileTest.java @@ -470,7 +470,7 @@ public void testGetRepresentationContentThrowsWhenExceedingMaxRetries() { } catch (BoxAPIException apiException) { assertEquals( apiException.getMessage(), - "Representation did non have a success status allowing it to be retrieved after 5 attempts" + "Representation did not have a success status allowing it to be retrieved after 5 attempts" ); } }