From 115a6a8b0c59c217925825ec6404930106552d46 Mon Sep 17 00:00:00 2001 From: Alberto Scotto Date: Sat, 16 Oct 2021 03:06:26 +0200 Subject: [PATCH 1/2] Upload: accept an InputStream besides a File This makes it possible to stream data coming from another server straight into the POST request to Gitlab, saving an extra, expensive, and useless IO operation on disk. --- .../java/org/gitlab4j/api/AbstractApi.java | 9 +++++ .../org/gitlab4j/api/GitLabApiClient.java | 36 +++++++++++++------ .../java/org/gitlab4j/api/ProjectApi.java | 20 +++++++++-- .../{TestFileUpload.java => TestUpload.java} | 31 +++++++++++++--- 4 files changed, 78 insertions(+), 18 deletions(-) rename src/test/java/org/gitlab4j/api/{TestFileUpload.java => TestUpload.java} (78%) diff --git a/src/main/java/org/gitlab4j/api/AbstractApi.java b/src/main/java/org/gitlab4j/api/AbstractApi.java index 4b4909173..196e6bdb4 100644 --- a/src/main/java/org/gitlab4j/api/AbstractApi.java +++ b/src/main/java/org/gitlab4j/api/AbstractApi.java @@ -1,6 +1,7 @@ package org.gitlab4j.api; import java.io.File; +import java.io.InputStream; import java.net.URL; import javax.ws.rs.NotAuthorizedException; @@ -382,6 +383,14 @@ protected Response upload(Response.Status expectedStatus, String name, File file } } + protected Response upload(Response.Status expectedStatus, String name, InputStream inputStream, String filename, String mediaType, Object... pathArgs) throws GitLabApiException { + try { + return validate(getApiClient().upload(name, inputStream, filename, mediaType, pathArgs), expectedStatus); + } catch (Exception e) { + throw handle(e); + } + } + /** * Perform a file upload with the specified File instance and path objects, returning * a ClientResponse instance with the data returned from the endpoint. diff --git a/src/main/java/org/gitlab4j/api/GitLabApiClient.java b/src/main/java/org/gitlab4j/api/GitLabApiClient.java index 04b9cd862..e130af570 100755 --- a/src/main/java/org/gitlab4j/api/GitLabApiClient.java +++ b/src/main/java/org/gitlab4j/api/GitLabApiClient.java @@ -2,6 +2,7 @@ import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.net.Socket; import java.net.URL; import java.security.GeneralSecurityException; @@ -12,7 +13,6 @@ import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; - import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; @@ -29,7 +29,6 @@ import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.StreamingOutput; - import org.gitlab4j.api.Constants.TokenType; import org.gitlab4j.api.GitLabApi.ApiVersion; import org.gitlab4j.api.utils.JacksonJson; @@ -38,11 +37,13 @@ import org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.client.ClientProperties; import org.glassfish.jersey.client.JerseyClientBuilder; +import org.glassfish.jersey.media.multipart.BodyPart; import org.glassfish.jersey.media.multipart.Boundary; import org.glassfish.jersey.media.multipart.FormDataMultiPart; import org.glassfish.jersey.media.multipart.MultiPart; import org.glassfish.jersey.media.multipart.MultiPartFeature; import org.glassfish.jersey.media.multipart.file.FileDataBodyPart; +import org.glassfish.jersey.media.multipart.file.StreamDataBodyPart; /** @@ -566,8 +567,7 @@ protected Response post(StreamingOutput stream, String mediaType, Object... path * @throws IOException if an error occurs while constructing the URL */ protected Response upload(String name, File fileToUpload, String mediaTypeString, Object... pathArgs) throws IOException { - URL url = getApiUrl(pathArgs); - return (upload(name, fileToUpload, mediaTypeString, null, url)); + return upload(name, fileToUpload, mediaTypeString, null, pathArgs); } /** @@ -600,23 +600,37 @@ protected Response upload(String name, File fileToUpload, String mediaTypeString * @throws IOException if an error occurs while constructing the URL */ protected Response upload(String name, File fileToUpload, String mediaTypeString, Form formData, URL url) throws IOException { + MediaType mediaType = (mediaTypeString != null ? MediaType.valueOf(mediaTypeString) : null); + FileDataBodyPart filePart = mediaType != null ? + new FileDataBodyPart(name, fileToUpload, mediaType) : + new FileDataBodyPart(name, fileToUpload); + return upload(filePart, formData, url); + } + protected Response upload(String name, InputStream inputStream, String filename, String mediaTypeString, Object... pathArgs) throws IOException { + URL url = getApiUrl(pathArgs); + return (upload(name, inputStream, filename, mediaTypeString, null, url)); + } + + protected Response upload(String name, InputStream inputStream, String filename, String mediaTypeString, Form formData, URL url) throws IOException { MediaType mediaType = (mediaTypeString != null ? MediaType.valueOf(mediaTypeString) : null); - try (FormDataMultiPart multiPart = new FormDataMultiPart()) { + StreamDataBodyPart streamDataBodyPart = mediaType != null ? + new StreamDataBodyPart(name, inputStream, filename, mediaType) : + new StreamDataBodyPart(name, inputStream, filename); + return upload(streamDataBodyPart, formData, url); + } + protected Response upload(BodyPart bodyPart, Form formData, URL url) throws IOException { + try (FormDataMultiPart multiPart = new FormDataMultiPart()) { if (formData != null) { - MultivaluedMap formParams = formData.asMap(); - formParams.forEach((key, values) -> { + formData.asMap().forEach((key, values) -> { if (values != null) { values.forEach(value -> multiPart.field(key, value)); } }); } - FileDataBodyPart filePart = mediaType != null ? - new FileDataBodyPart(name, fileToUpload, mediaType) : - new FileDataBodyPart(name, fileToUpload); - multiPart.bodyPart(filePart); + multiPart.bodyPart(bodyPart); final Entity entity = Entity.entity(multiPart, Boundary.addBoundary(multiPart.getMediaType())); return (invocation(url, null).post(entity)); } diff --git a/src/main/java/org/gitlab4j/api/ProjectApi.java b/src/main/java/org/gitlab4j/api/ProjectApi.java index 626181e73..959201b54 100644 --- a/src/main/java/org/gitlab4j/api/ProjectApi.java +++ b/src/main/java/org/gitlab4j/api/ProjectApi.java @@ -24,6 +24,7 @@ package org.gitlab4j.api; import java.io.File; +import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Date; @@ -32,12 +33,10 @@ import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; - import javax.ws.rs.core.Form; import javax.ws.rs.core.GenericType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; - import org.gitlab4j.api.GitLabApi.ApiVersion; import org.gitlab4j.api.models.AccessLevel; import org.gitlab4j.api.models.AccessRequest; @@ -2562,6 +2561,23 @@ public FileUpload uploadFile(Object projectIdOrPath, File fileToUpload, String m return (response.readEntity(FileUpload.class)); } + /** + * Uploads some data in an {@link InputStream} to the specified project, + * to be used in an issue or merge request description, or a comment. + * + *
GitLab Endpoint: POST /projects/:id/uploads
+ * + * @param projectIdOrPath the project in the form of an Integer(ID), String(path), or Project instance, required + * @param inputStream the data to upload, required + * @param mediaType the media type of the file to upload, required + * @return a FileUpload instance with information on the just uploaded file + * @throws GitLabApiException if any exception occurs + */ + public FileUpload uploadFile(Object projectIdOrPath, InputStream inputStream, String filename, String mediaType) throws GitLabApiException { + Response response = upload(Response.Status.CREATED, "file", inputStream, filename, mediaType, "projects", getProjectIdOrPath(projectIdOrPath), "uploads"); + return (response.readEntity(FileUpload.class)); + } + /** * Get the project's push rules. * diff --git a/src/test/java/org/gitlab4j/api/TestFileUpload.java b/src/test/java/org/gitlab4j/api/TestUpload.java similarity index 78% rename from src/test/java/org/gitlab4j/api/TestFileUpload.java rename to src/test/java/org/gitlab4j/api/TestUpload.java index c607fb6b0..c55d5fc27 100644 --- a/src/test/java/org/gitlab4j/api/TestFileUpload.java +++ b/src/test/java/org/gitlab4j/api/TestUpload.java @@ -1,12 +1,16 @@ package org.gitlab4j.api; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertNotNull; import static org.junit.Assume.assumeNotNull; import static org.junit.Assume.assumeTrue; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.util.Map; - import org.gitlab4j.api.models.FileUpload; import org.gitlab4j.api.models.Project; import org.junit.Before; @@ -18,17 +22,17 @@ /** * In order for these tests to run you must set the following properties in test-gitlab4j.properties - * + * * TEST_NAMESPACE * TEST_PROJECT_NAME * TEST_HOST_URL * TEST_PRIVATE_TOKEN - * + * * If any of the above are NULL, all tests in this class will be skipped. */ @Category(IntegrationTest.class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class TestFileUpload extends AbstractIntegrationTest { +public class TestUpload extends AbstractIntegrationTest { // The following needs to be set to your test repository private static final String TEST_PROXY_URI = HelperUtils.getProperty("TEST_PROXY_URI"); @@ -37,7 +41,7 @@ public class TestFileUpload extends AbstractIntegrationTest { private static GitLabApi gitLabApi; - public TestFileUpload() { + public TestUpload() { super(); } @@ -93,4 +97,21 @@ public void testFileUploadWithProxy() throws GitLabApiException { gitLabApi.close(); } + + @Test + public void testInputStreamUpload() throws GitLabApiException, FileNotFoundException { + + Project project = gitLabApi.getProjectApi().getProject(TEST_NAMESPACE, TEST_PROJECT_NAME); + assertNotNull(project); + + String filename = "README.md"; + File fileToUpload = new File(filename); + FileUpload fileUpload = gitLabApi.getProjectApi().uploadFile( + project.getId(), new FileInputStream(fileToUpload), filename, null); + + assertNotNull(fileUpload); + assertThat(fileUpload.getUrl(), endsWith(filename)); + assertThat(fileUpload.getAlt(), equalTo(filename)); + } + } From 1a1c3b2189091c42cb6f77971d3ec180d356ba45 Mon Sep 17 00:00:00 2001 From: Alberto Scotto Date: Wed, 24 Nov 2021 14:49:22 +0100 Subject: [PATCH 2/2] Fixes as per review comments --- src/main/java/org/gitlab4j/api/AbstractApi.java | 6 +++--- .../java/org/gitlab4j/api/GitLabApiClient.java | 16 +++++----------- src/main/java/org/gitlab4j/api/ProjectApi.java | 3 +-- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/gitlab4j/api/AbstractApi.java b/src/main/java/org/gitlab4j/api/AbstractApi.java index 196e6bdb4..b337f50d6 100644 --- a/src/main/java/org/gitlab4j/api/AbstractApi.java +++ b/src/main/java/org/gitlab4j/api/AbstractApi.java @@ -370,7 +370,7 @@ protected Response post(Response.Status expectedStatus, Form formData, URL url) * @param expectedStatus the HTTP status that should be returned from the server * @param name the name for the form field that contains the file name * @param fileToUpload a File instance pointing to the file to upload - * @param mediaType the content-type of the uploaded file, if null will be determined from fileToUpload + * @param mediaType unused; will be removed in the next major version * @param pathArgs variable list of arguments used to build the URI * @return a ClientResponse instance with the data returned from the endpoint * @throws GitLabApiException if any exception occurs during execution @@ -398,7 +398,7 @@ protected Response upload(Response.Status expectedStatus, String name, InputStre * @param expectedStatus the HTTP status that should be returned from the server * @param name the name for the form field that contains the file name * @param fileToUpload a File instance pointing to the file to upload - * @param mediaType the content-type of the uploaded file, if null will be determined from fileToUpload + * @param mediaType unused; will be removed in the next major version * @param url the fully formed path to the GitLab API endpoint * @return a ClientResponse instance with the data returned from the endpoint * @throws GitLabApiException if any exception occurs during execution @@ -418,7 +418,7 @@ protected Response upload(Response.Status expectedStatus, String name, File file * @param expectedStatus the HTTP status that should be returned from the server * @param name the name for the form field that contains the file name * @param fileToUpload a File instance pointing to the file to upload - * @param mediaType the content-type of the uploaded file, if null will be determined from fileToUpload + * @param mediaType unused; will be removed in the next major version * @param formData the Form containing the name/value pairs * @param url the fully formed path to the GitLab API endpoint * @return a ClientResponse instance with the data returned from the endpoint diff --git a/src/main/java/org/gitlab4j/api/GitLabApiClient.java b/src/main/java/org/gitlab4j/api/GitLabApiClient.java index e130af570..4fd047309 100755 --- a/src/main/java/org/gitlab4j/api/GitLabApiClient.java +++ b/src/main/java/org/gitlab4j/api/GitLabApiClient.java @@ -561,7 +561,7 @@ protected Response post(StreamingOutput stream, String mediaType, Object... path * * @param name the name for the form field that contains the file name * @param fileToUpload a File instance pointing to the file to upload - * @param mediaTypeString the content-type of the uploaded file, if null will be determined from fileToUpload + * @param mediaTypeString unused; will be removed in the next major version * @param pathArgs variable list of arguments used to build the URI * @return a ClientResponse instance with the data returned from the endpoint * @throws IOException if an error occurs while constructing the URL @@ -576,7 +576,7 @@ protected Response upload(String name, File fileToUpload, String mediaTypeString * * @param name the name for the form field that contains the file name * @param fileToUpload a File instance pointing to the file to upload - * @param mediaTypeString the content-type of the uploaded file, if null will be determined from fileToUpload + * @param mediaTypeString unused; will be removed in the next major version * @param formData the Form containing the name/value pairs * @param pathArgs variable list of arguments used to build the URI * @return a ClientResponse instance with the data returned from the endpoint @@ -593,17 +593,14 @@ protected Response upload(String name, File fileToUpload, String mediaTypeString * * @param name the name for the form field that contains the file name * @param fileToUpload a File instance pointing to the file to upload - * @param mediaTypeString the content-type of the uploaded file, if null will be determined from fileToUpload + * @param mediaTypeString unused; will be removed in the next major version * @param formData the Form containing the name/value pairs * @param url the fully formed path to the GitLab API endpoint * @return a ClientResponse instance with the data returned from the endpoint * @throws IOException if an error occurs while constructing the URL */ protected Response upload(String name, File fileToUpload, String mediaTypeString, Form formData, URL url) throws IOException { - MediaType mediaType = (mediaTypeString != null ? MediaType.valueOf(mediaTypeString) : null); - FileDataBodyPart filePart = mediaType != null ? - new FileDataBodyPart(name, fileToUpload, mediaType) : - new FileDataBodyPart(name, fileToUpload); + FileDataBodyPart filePart = new FileDataBodyPart(name, fileToUpload); return upload(filePart, formData, url); } @@ -613,10 +610,7 @@ protected Response upload(String name, InputStream inputStream, String filename, } protected Response upload(String name, InputStream inputStream, String filename, String mediaTypeString, Form formData, URL url) throws IOException { - MediaType mediaType = (mediaTypeString != null ? MediaType.valueOf(mediaTypeString) : null); - StreamDataBodyPart streamDataBodyPart = mediaType != null ? - new StreamDataBodyPart(name, inputStream, filename, mediaType) : - new StreamDataBodyPart(name, inputStream, filename); + StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart(name, inputStream, filename); return upload(streamDataBodyPart, formData, url); } diff --git a/src/main/java/org/gitlab4j/api/ProjectApi.java b/src/main/java/org/gitlab4j/api/ProjectApi.java index 959201b54..0dddb489b 100644 --- a/src/main/java/org/gitlab4j/api/ProjectApi.java +++ b/src/main/java/org/gitlab4j/api/ProjectApi.java @@ -2552,7 +2552,7 @@ public FileUpload uploadFile(Object projectIdOrPath, File fileToUpload) throws G * * @param projectIdOrPath the project in the form of an Integer(ID), String(path), or Project instance, required * @param fileToUpload the File instance of the file to upload, required - * @param mediaType the media type of the file to upload, optional + * @param mediaType unused; will be removed in the next major version * @return a FileUpload instance with information on the just uploaded file * @throws GitLabApiException if any exception occurs */ @@ -2569,7 +2569,6 @@ public FileUpload uploadFile(Object projectIdOrPath, File fileToUpload, String m * * @param projectIdOrPath the project in the form of an Integer(ID), String(path), or Project instance, required * @param inputStream the data to upload, required - * @param mediaType the media type of the file to upload, required * @return a FileUpload instance with information on the just uploaded file * @throws GitLabApiException if any exception occurs */