-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add configurable filesystem binary storage (fix #860) #864
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds configurable filesystem binary storage to the HAPI FHIR JPA Server Starter, allowing users to store binary data on disk instead of in the database without requiring custom beans.
- Introduces
binary_storage_mode
andbinary_storage_filesystem_base_directory
configuration properties - Implements automatic 100 KB inline threshold when filesystem mode is enabled without explicit size configuration
- Adds comprehensive test coverage for the new binary storage configuration options
Reviewed Changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
File | Description |
---|---|
AppProperties.java | Adds new enum and properties for binary storage mode and filesystem base directory configuration |
FhirServerConfigCommon.java | Implements logic to instantiate appropriate binary storage service based on configuration with validation |
application.yaml | Documents the new configuration options with examples and usage notes |
README.md | Adds documentation section explaining how to configure filesystem binary storage |
FhirServerConfigCommonBinaryStorageTest.java | Comprehensive test suite covering all configuration scenarios and validation |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
if (appProperties.getMax_binary_size() != null) { | ||
filesystemSvc.setMaximumBinarySize(appProperties.getMax_binary_size().longValue()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent type handling for max_binary_size. The filesystem implementation uses .longValue()
while the database implementation on line 373 passes the Integer directly. This could cause type compatibility issues.
Copilot uses AI. Check for mistakes.
Integer inlineResourceThreshold = appProperties.getInline_resource_storage_below_size(); | ||
if (inlineResourceThreshold == null | ||
&& appProperties.getBinary_storage_mode() == AppProperties.BinaryStorageMode.FILESYSTEM) { | ||
inlineResourceThreshold = resolveFilesystemMinimumBinarySize(appProperties); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic for resolving the inline resource threshold is duplicated between jpaStorageSettings()
and binaryStorageSvc()
methods. Consider extracting this threshold resolution logic to avoid code duplication.
Integer inlineResourceThreshold = appProperties.getInline_resource_storage_below_size(); | |
if (inlineResourceThreshold == null | |
&& appProperties.getBinary_storage_mode() == AppProperties.BinaryStorageMode.FILESYSTEM) { | |
inlineResourceThreshold = resolveFilesystemMinimumBinarySize(appProperties); | |
} | |
Integer inlineResourceThreshold = resolveInlineResourceThreshold(appProperties); |
Copilot uses AI. Check for mistakes.
@Bean | ||
public IBinaryStorageSvc binaryStorageSvc(AppProperties appProperties) { | ||
DatabaseBinaryContentStorageSvcImpl binaryStorageSvc = new DatabaseBinaryContentStorageSvcImpl(); | ||
IBinaryStorageSvc binaryStorageSvc; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementations of IBinaryStorageSvc
should be defined as Beans and injected as such.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comments above
if (appProperties.getMax_binary_size() != null) { | ||
binaryStorageSvc.setMaximumBinarySize(appProperties.getMax_binary_size()); | ||
@Lazy | ||
@Bean |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of having these declared as lazy you can follow the approach like in
hapi-fhir-jpaserver-starter/src/main/java/ca/uhn/fhir/jpa/starter/mcp/McpServerConfig.java
Line 30 in a499603
@ConditionalOnProperty( |
Apologies, tried to rebase to avoid the merging issues, but may have added complexity in doing so. Hopefully all is clear |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
Looks good so far. Please add an integration test that proves it works when not using the default values. |
With integration tests I mean an entire passthrough like with the rest of the integration tests - which are more end2end. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
# validation: | ||
# requests_enabled: true | ||
# responses_enabled: true | ||
# binary_storage_enabled: true | ||
# binary_storage_mode: FILESYSTEM | ||
# binary_storage_filesystem_base_directory: /binstore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent indentation inside commented sample properties (extra spaces after #) may confuse users when uncommenting, potentially producing mis-indented YAML. Align these comment lines with surrounding examples (e.g., ' # validation:' and nested keys indented two spaces after the #) for consistency.
# validation: | |
# requests_enabled: true | |
# responses_enabled: true | |
# binary_storage_enabled: true | |
# binary_storage_mode: FILESYSTEM | |
# binary_storage_filesystem_base_directory: /binstore | |
# validation: | |
# requests_enabled: true | |
# responses_enabled: true | |
# binary_storage_enabled: true | |
# binary_storage_mode: FILESYSTEM | |
# binary_storage_filesystem_base_directory: /binstore |
Copilot uses AI. Check for mistakes.
private Path baseDir() throws IOException { | ||
Path baseDir = Paths.get(BASE_DIRECTORY).toAbsolutePath(); | ||
Files.createDirectories(baseDir); | ||
return baseDir; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicated baseDir() implementations in two test classes increase maintenance overhead. Extract this method into the BaseBinaryStorageIntegrationTest (parameterized by a supplied directory constant) or a shared utility to eliminate duplication.
Copilot uses AI. Check for mistakes.
private Path baseDir() throws IOException { | ||
Path baseDir = Paths.get(BASE_DIRECTORY).toAbsolutePath(); | ||
Files.createDirectories(baseDir); | ||
return baseDir; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicated baseDir() implementations in two test classes increase maintenance overhead. Extract this method into the BaseBinaryStorageIntegrationTest (parameterized by a supplied directory constant) or a shared utility to eliminate duplication.
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
@Bean | ||
public IBinaryStorageSvc binaryStorageSvc(AppProperties appProperties) { | ||
DatabaseBinaryContentStorageSvcImpl binaryStorageSvc = new DatabaseBinaryContentStorageSvcImpl(); | ||
@ConditionalOnProperty(prefix = "hapi.fhir", name = "binary_storage_mode", havingValue = "FILESYSTEM") | ||
public FilesystemBinaryStorageSvcImpl filesystemBinaryStorageSvc(AppProperties appProperties) { | ||
String baseDirectory = appProperties.getBinary_storage_filesystem_base_directory(); | ||
Assert.hasText( | ||
baseDirectory, | ||
"binary_storage_filesystem_base_directory must be provided when binary_storage_mode=FILESYSTEM"); | ||
|
||
FilesystemBinaryStorageSvcImpl filesystemSvc = new FilesystemBinaryStorageSvcImpl(baseDirectory); | ||
Integer inlineResourceThreshold = resolveInlineResourceThreshold(appProperties); | ||
int minimumBinarySize = | ||
inlineResourceThreshold == null ? DEFAULT_FILESYSTEM_INLINE_THRESHOLD : inlineResourceThreshold; | ||
filesystemSvc.setMinimumBinarySize(minimumBinarySize); | ||
|
||
Integer maxBinarySize = appProperties.getMax_binary_size(); | ||
if (maxBinarySize != null) { | ||
filesystemSvc.setMaximumBinarySize(maxBinarySize.longValue()); | ||
} | ||
|
||
return filesystemSvc; | ||
} | ||
|
||
if (appProperties.getMax_binary_size() != null) { | ||
binaryStorageSvc.setMaximumBinarySize(appProperties.getMax_binary_size()); | ||
@Bean | ||
@ConditionalOnProperty( | ||
prefix = "hapi.fhir", | ||
name = "binary_storage_mode", | ||
havingValue = "DATABASE", | ||
matchIfMissing = true) | ||
public DatabaseBinaryContentStorageSvcImpl databaseBinaryStorageSvc(AppProperties appProperties) { | ||
DatabaseBinaryContentStorageSvcImpl databaseSvc = new DatabaseBinaryContentStorageSvcImpl(); | ||
Integer maxBinarySize = appProperties.getMax_binary_size(); | ||
if (maxBinarySize != null) { | ||
databaseSvc.setMaximumBinarySize(maxBinarySize.longValue()); | ||
} | ||
return databaseSvc; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously there was a single bean named binaryStorageSvc (returning IBinaryStorageSvc); replacing it with two differently named concrete beans removes that bean name and can break external customizations relying on @qualifier("binaryStorageSvc") or bean name overrides. Consider adding a unifying @bean(name = "binaryStorageSvc") that returns IBinaryStorageSvc and delegates to the active implementation, or add @primary plus an alias to preserve backward compatibility.
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no - that is a deliberate choice.
Integer inlineResourceThreshold = resolveInlineResourceThreshold(appProperties); | ||
int minimumBinarySize = | ||
inlineResourceThreshold == null ? DEFAULT_FILESYSTEM_INLINE_THRESHOLD : inlineResourceThreshold; | ||
filesystemSvc.setMinimumBinarySize(minimumBinarySize); | ||
|
||
Integer maxBinarySize = appProperties.getMax_binary_size(); | ||
if (maxBinarySize != null) { | ||
filesystemSvc.setMaximumBinarySize(maxBinarySize.longValue()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Negative values for inline_resource_storage_below_size or max_binary_size are not validated and could lead to unexpected behavior. Add explicit non-negative assertions (e.g., Assert.isTrue(value >= 0, "...") ) before applying these values.
Copilot uses AI. Check for mistakes.
I see you disagree with copilot on one point, would you like me to implement the non-negative assertion tests? Anything else to adjust? |
Overview
Key Changes
Testing