diff --git a/pom.xml b/pom.xml index 665dbb4f..3211255a 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.hisp dhis2-java-client - 2.6.9 + 2.6.10 jar DHIS 2 API client for Java diff --git a/src/main/java/org/hisp/dhis/BaseDhis2.java b/src/main/java/org/hisp/dhis/BaseDhis2.java index d6ef3585..362dcc2a 100644 --- a/src/main/java/org/hisp/dhis/BaseDhis2.java +++ b/src/main/java/org/hisp/dhis/BaseDhis2.java @@ -1079,6 +1079,19 @@ protected HttpPost getPostRequest(URI url) { return request; } + /** + * Returns a HTTP post request with a multipart entity for the given URL, e.g. for file uploads. + * + * @param url the {@link URI}. + * @param entity the multipart {@link HttpEntity}. + * @return a {@link HttpPost} request. + */ + protected HttpPost getMultipartPostRequest(URI url, HttpEntity entity) { + HttpPost request = withAuth(new HttpPost(url)); + request.setEntity(entity); + return request; + } + /** * Retrieves an object using HTTP GET. * @@ -1447,16 +1460,7 @@ protected Response addToCollection(String path, String id, String collection, St .appendPath(collection) .appendPath(item)); - Response response = executeRequest(new HttpPost(url)); - - Status status = - response != null - && response.getHttpStatus() != null - && response.getHttpStatus().is2xxSuccessful() - ? Status.OK - : Status.ERROR; - - return new Response(status, response.getHttpStatusCode(), response.getMessage()); + return executeRequest(new HttpPost(url)); } /** diff --git a/src/main/java/org/hisp/dhis/Dhis2.java b/src/main/java/org/hisp/dhis/Dhis2.java index 370de5d6..ab294f09 100644 --- a/src/main/java/org/hisp/dhis/Dhis2.java +++ b/src/main/java/org/hisp/dhis/Dhis2.java @@ -70,7 +70,9 @@ import org.apache.hc.client5.http.HttpResponseException; import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.io.entity.FileEntity; import org.apache.hc.core5.http.io.entity.InputStreamEntity; import org.apache.hc.core5.http.io.entity.StringEntity; @@ -183,6 +185,7 @@ import org.hisp.dhis.response.data.ImportSummaryResponse; import org.hisp.dhis.response.datavalueset.DataValueSetResponse; import org.hisp.dhis.response.event.EventResponse; +import org.hisp.dhis.response.fileresource.FileResourceResponse; import org.hisp.dhis.response.job.JobCategory; import org.hisp.dhis.response.job.JobInfoResponse; import org.hisp.dhis.response.job.JobNotification; @@ -3919,6 +3922,42 @@ public List getFileResources(Query query) { .getFileResources(); } + /** + * Creates a {@link FileResource} by uploading the given file. + * + * @param file the {@link File} to upload. + * @return a {@link FileResourceResponse}. + * @throws Dhis2ClientException if the request was invalid. + */ + public FileResourceResponse saveFileResource(File file) { + HttpEntity entity = MultipartEntityBuilder.create().addBinaryBody("file", file).build(); + + URI url = HttpUtils.build(config.getResolvedUriBuilder().appendPath(PATH_FILE_RESOURCES)); + + return executeRequest(getMultipartPostRequest(url, entity), FileResourceResponse.class); + } + + /** + * Creates a {@link FileResource} by uploading the content of the given {@link InputStream}. + * + * @param fileName the name of the file. + * @param contentType the content type of the file, e.g. "image/png". + * @param input the {@link InputStream} of the file content. + * @return a {@link FileResourceResponse}. + * @throws Dhis2ClientException if the request was invalid. + */ + public FileResourceResponse saveFileResource( + String fileName, String contentType, InputStream input) { + HttpEntity entity = + MultipartEntityBuilder.create() + .addBinaryBody("file", input, ContentType.parse(contentType), fileName) + .build(); + + URI url = HttpUtils.build(config.getResolvedUriBuilder().appendPath(PATH_FILE_RESOURCES)); + + return executeRequest(getMultipartPostRequest(url, entity), FileResourceResponse.class); + } + /** * Downloads file content from the given URL. * @@ -4029,10 +4068,12 @@ public byte[] getEventFile(String eventUid, String dataElementUid) { HttpUtils.build( config .getResolvedUriBuilder() + .appendPath(PATH_TRACKER) .appendPath(PATH_EVENTS) - .appendPath("files") - .addParameter("eventUid", eventUid) - .addParameter("dataElementUid", dataElementUid)); + .appendPath(eventUid) + .appendPath("dataValues") + .appendPath(dataElementUid) + .appendPath("file")); return downloadFile(uri, "Failed to download event file"); } diff --git a/src/main/java/org/hisp/dhis/response/fileresource/FileResourceReport.java b/src/main/java/org/hisp/dhis/response/fileresource/FileResourceReport.java new file mode 100644 index 00000000..b61effec --- /dev/null +++ b/src/main/java/org/hisp/dhis/response/fileresource/FileResourceReport.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2004-2025, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.hisp.dhis.response.fileresource; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.hisp.dhis.model.FileResource; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class FileResourceReport { + @JsonProperty private String responseType; + + @JsonProperty private FileResource fileResource; +} diff --git a/src/main/java/org/hisp/dhis/response/fileresource/FileResourceResponse.java b/src/main/java/org/hisp/dhis/response/fileresource/FileResourceResponse.java new file mode 100644 index 00000000..d5d49af0 --- /dev/null +++ b/src/main/java/org/hisp/dhis/response/fileresource/FileResourceResponse.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2004-2025, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.hisp.dhis.response.fileresource; + +import static org.hisp.dhis.util.TextUtils.newToStringBuilder; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hisp.dhis.response.Response; + +@Getter +@Setter +@NoArgsConstructor +public class FileResourceResponse extends Response { + @JsonProperty protected FileResourceReport response; + + @Override + public String toString() { + return newToStringBuilder(this, super.toString()).append("response", response).toString(); + } +} diff --git a/src/test/java/org/hisp/dhis/FileResourceApiTest.java b/src/test/java/org/hisp/dhis/FileResourceApiTest.java index 8277197c..fb3b83de 100644 --- a/src/test/java/org/hisp/dhis/FileResourceApiTest.java +++ b/src/test/java/org/hisp/dhis/FileResourceApiTest.java @@ -32,9 +32,17 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.List; import org.hisp.dhis.model.FileResource; import org.hisp.dhis.query.Query; +import org.hisp.dhis.response.fileresource.FileResourceReport; +import org.hisp.dhis.response.fileresource.FileResourceResponse; import org.hisp.dhis.support.TestTags; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -101,4 +109,51 @@ void testGetEventFile() { assertNotNull(data); assertTrue(data.length > 0); } + + @Test + void testSaveFileResource() throws IOException { + Dhis2 dhis2 = new Dhis2(TestFixture.DEFAULT_CONFIG); + + File file = File.createTempFile("dhis2-java-client-test", ".txt"); + file.deleteOnExit(); + Files.writeString(file.toPath(), "Dhis2 Java client file resource test content"); + + FileResourceResponse response = dhis2.saveFileResource(file); + + assertNotNull(response); + assertTrue(response.isStatusOk()); + + FileResourceReport report = response.getResponse(); + + assertNotNull(report); + + FileResource fileResource = report.getFileResource(); + + assertNotNull(fileResource); + assertNotBlank(fileResource.getId()); + } + + @Test + void testSaveFileResourceFromInputStream() { + Dhis2 dhis2 = new Dhis2(TestFixture.DEFAULT_CONFIG); + + InputStream input = + new ByteArrayInputStream( + "Dhis2 Java client file resource test content".getBytes(StandardCharsets.UTF_8)); + + FileResourceResponse response = + dhis2.saveFileResource("dhis2-java-client-test.txt", "text/plain", input); + + assertNotNull(response); + assertTrue(response.isStatusOk()); + + FileResourceReport report = response.getResponse(); + + assertNotNull(report); + + FileResource fileResource = report.getFileResource(); + + assertNotNull(fileResource); + assertNotBlank(fileResource.getId()); + } } diff --git a/src/test/java/org/hisp/dhis/TestFixture.java b/src/test/java/org/hisp/dhis/TestFixture.java index dc910f52..40c83f89 100644 --- a/src/test/java/org/hisp/dhis/TestFixture.java +++ b/src/test/java/org/hisp/dhis/TestFixture.java @@ -38,7 +38,7 @@ public final class TestFixture { public static final String DEV_URL = "https://play.im.dhis2.org/dev"; - public static final String V41_URL = "https://play.im.dhis2.org/stable-2-41-8-1"; + public static final String V41_URL = "https://play.im.dhis2.org/stable-2-41-9"; public static final String V42_URL = "https://play.im.dhis2.org/stable-2-42-4-1";