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";