Skip to content

feat: Replace JClouds with AWS SDK + File NIO [DHIS2-20648]#23918

Merged
david-mackessy merged 26 commits into
masterfrom
DHIS2-20648_4
Jun 2, 2026
Merged

feat: Replace JClouds with AWS SDK + File NIO [DHIS2-20648]#23918
david-mackessy merged 26 commits into
masterfrom
DHIS2-20648_4

Conversation

@david-mackessy
Copy link
Copy Markdown
Contributor

@david-mackessy david-mackessy commented May 15, 2026

AI assisted 🤖

Summary

Replaces the JClouds object storage implementation. This was primarily planned due to the archiving of the JCLouds library. Replacing it has resulted in many positives:

  • introduction of BlobStoreService interface
  • dedicated handling of different FileStoreProvider types
  • simpler extension path for different FileStoreProvider

JClouds tried to handle all variants of FileStoreProvider (noted in the table below) but it did so in an inefficient way:

  • needed to know type of FileStoreProvider to decide what to do
  • separate code paths for different FileStoreProvider types
  • hard to make changes for one FileStoreProvider type without affecting others

New implementations

Provider New Implementation
aws-s3 AWS SDK v2
s3 AWS SDK v2
filesystem Java File NIO
transient Java ConcurrentHashMap

Rename

  • Replaced any class with JClouds prefix with BlobStore
  • AppStorageSource.JCLOUDS -> AppStorageSource.BLOB_STORE

Removed

JCloudsStore implementation

Added

Implementations for:

  • S3BlobStoreService
  • FileSystemBlobStoreService
  • TransientBlobStoreService

Fix

As part of the replacement, recursive deletes now operate correctly. Previous issue of some files being left behind depending on provider type. Haven't seen any evidence of this while testing.

Testing

Automated

  • Unit tests
  • Integration tests

Manual

Tested providers (s3, filesystem, transient) with the scenarios:

Scenario Result
start server with no bundled apps installed all apps installed
browse through apps in UI no issues loading
start server with no default icons PATCH /api/icons -> icons installed
retrieve icon 2g_negative icon retrieved
upload data value file file saved
import metadata async job data saved
update existing bundled app deleted old version & installed new version
upload custom app installed
get index.html from custom app retrieved
delete custom app deleted

@david-mackessy david-mackessy changed the title Dhis2 20648 4 feat: Replace JClouds with AWS SDK + File NIO [DHIS2-20648] May 18, 2026
Copy link
Copy Markdown
Contributor

@jbee jbee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some details to sort out:

  • enum name change (migration needed?)
  • details store API error contract (null vs exception, which exception, consistency)

Comment thread dhis-2/dhis-api/src/main/java/org/hisp/dhis/appmanager/AppStorageSource.java Outdated
Copy link
Copy Markdown
Contributor

@Philip-Larsen-Donnelly Philip-Larsen-Donnelly left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The slow speed of the S3 uploads* makes this painful to test, but apart from that I didn't find any obvious issues with the storage.
Tested apps, icons, avatars and data values.
*Note - everything is slow. Even the icon fix patch too a couple of minutes to populate icons and the API call timed out after one minute.

@david-mackessy
Copy link
Copy Markdown
Contributor Author

The slow speed of the S3 uploads* makes this painful to test, but apart from that I didn't find any obvious issues with the storage. Tested apps, icons, avatars and data values. *Note - everything is slow. Even the icon fix patch too a couple of minutes to populate icons and the API call timed out after one minute.

This testing was extremely valuable @Philip-Larsen-Donnelly, thank you. How features use the BlobStore is very ad-hoc. When issues are known (like bundled app install or icon patch), these can be looked at & improved 👍

* ZipFile.getInputStream(zipEntry)} used by app install) the wrapper throws.
*
* <p>If the caller's stream is non-resetable we pre-buffer it into a byte array and use {@link
* RequestBody#fromBytes}, which is safely re-readable. Resetable streams (file streams, byte
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After digging a bit (After some tips from Claude); I don't think file stream (FileInputStream) is actually resettable, as it doesn't implement the mark and reset methods. Not a critical thing per se, but it makes the next point more relevant:

If the stream is not resettable, we read the stream into a buffer, effectively reading the whole file into our heap. We might want to have a hard limit somewhere, either based on heap space or just a hard limit on individual file sizes; Cause now there's a way to upload files big enough to run us OOM, either with one big file, or with several, simultaneous medium files.

However, looking at "normal" use and expectations; And the fact that the user can set a sane file size limit in dhis.conf; I don't see this issue as a blocker, rather something to keep in mind; Maybe add to documentation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can incorporate the system max file size here as a safeguard?
max.file_upload_size - default is 10MB.
At least then we don't interfere with system restrictions, which is probably what we want.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Jun 2, 2026

@david-mackessy david-mackessy marked this pull request as ready for review June 2, 2026 09:10
@david-mackessy david-mackessy merged commit 74cb43f into master Jun 2, 2026
23 checks passed
@david-mackessy david-mackessy deleted the DHIS2-20648_4 branch June 2, 2026 09:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants