From 2a636328a0ff6b663552e4eb051b4434633d9a1d Mon Sep 17 00:00:00 2001
From: Avgustin Marinov
Date: Tue, 2 Sep 2025 16:08:14 +0300
Subject: [PATCH] 20250828 cleanup (#2639)
* Cleanup
* Refactor artifact management
---
.3rd-party/README.md | 18 +-
.../reusable_workflow_license-scan.yaml | 2 +-
docker/README.md | 48 +++--
docker/build/README.md | 2 -
hawkbit-artifact/README.md | 5 +
.../hawkbit-artifact-api/README.md | 9 +-
.../AbstractArtifactStorage.java} | 62 +++---
.../ArtifactStorage.java} | 25 +--
.../encryption/ArtifactEncryption.java | 8 +-
.../ArtifactEncryptionSecretsStorage.java} | 20 +-
.../encryption/ArtifactEncryptionService.java | 13 +-
...ArtifactBinaryNoLongerExistsException.java | 26 +--
.../ArtifactBinaryNotFoundException.java | 24 +--
.../ArtifactDeleteFailedException.java | 22 +-
.../ArtifactEncryptionFailedException.java | 12 +-
...rtifactEncryptionUnsupportedException.java | 16 +-
.../exception/ArtifactStoreException.java | 12 +-
.../ArtifactUploadFailedException.java | 31 +--
.../FileSizeQuotaExceededException.java | 36 ++--
.../exception/HashNotMatchException.java | 8 +-
.../StorageQuotaExceededException.java | 26 +--
.../artifact/model/ArtifactHashes.java | 15 ++
.../artifact/model/ArtifactStream.java | 51 +++++
.../model/StoredArtifactInfo.java} | 17 +-
.../artifact/urlresolver/ArtifactUrl.java | 15 ++
.../urlresolver/ArtifactUrlResolver.java | 64 ++++++
.../PropertyBasedArtifactUrlResolver.java | 152 +++++++++++++
...tyBasedArtifactUrlResolverProperties.java} | 19 +-
.../repository/artifact/model/DbArtifact.java | 46 ----
.../artifact/model/DbArtifactHash.java | 36 ----
.../artifact/urlhandler/ApiType.java | 31 ---
.../artifact/urlhandler/ArtifactUrl.java | 36 ----
.../urlhandler/ArtifactUrlHandler.java | 39 ----
.../artifact/urlhandler/Base62Util.java | 67 ------
.../PropertyBasedArtifactUrlHandler.java | 203 ------------------
.../artifact/urlhandler/URLPlaceholder.java | 71 ------
.../ArtifactEncryptionServiceTest.java | 4 +-
...PropertyBasedArtifactUrlResolverTest.java} | 140 +++++-------
.../artifact/urlhandler/Base62UtilTest.java | 43 ----
.../urlhandler/URLPlaceholderTest.java | 93 --------
.../hawkbit-artifact-fs/README.md | 8 +
.../pom.xml | 2 +-
.../artifact/fs/FileArtifactProperties.java} | 4 +-
.../artifact/fs/FileArtifactStorage.java | 114 ++++++++++
.../fs/FileArtifactStorageConfiguration.java} | 20 +-
...ot.autoconfigure.AutoConfiguration.imports | 1 +
.../artifact/fs/FileArtifactStorageTest.java} | 43 ++--
.../README.md | 18 --
.../ArtifactFileNotFoundException.java | 25 ---
.../artifact/ArtifactFilesystem.java | 52 -----
.../ArtifactFilesystemRepository.java | 115 ----------
...ot.autoconfigure.AutoConfiguration.imports | 1 -
.../artifact/ArtifactFilesystemTest.java | 53 -----
hawkbit-artifact/pom.xml | 2 +-
...ArtifactUrlResolverAutoConfiguration.java} | 24 +--
.../cache/CacheAutoConfiguration.java | 15 +-
.../autoconfigure/cache/CacheProperties.java | 2 +-
.../EventPublisherAutoConfiguration.java | 2 +-
.../scheduling/ExecutorAutoConfiguration.java | 16 +-
...roperties.java => ExecutorProperties.java} | 4 +-
...taticUserManagementAutoConfiguration.java} | 5 +-
...ot.autoconfigure.AutoConfiguration.imports | 4 +-
.../hawkbit-artifactdl-defaults.properties | 9 +-
hawkbit-core/README.md | 2 +-
.../exception/AbstractServerRtException.java | 63 +-----
.../eclipse/hawkbit/tenancy/TenantAware.java | 2 +-
.../TenantAwareAuthenticationDetails.java | 21 +-
.../tenancy/TenantMetricsConfiguration.java | 4 +-
.../cache/TenantAwareCacheManager.java | 14 +-
.../cache/TenantCacheManager.java} | 4 +-
.../eclipse/hawkbit/utils/ObjectCopyUtil.java | 69 +++---
...eldsTest.java => RsqlQueryFieldsTest.java} | 6 +-
.../ddi/json/model/DdiActionHistory.java | 4 +-
.../model/DdiActivateAutoConfirmation.java | 11 +-
.../hawkbit/ddi/json/model/DdiArtifact.java | 6 +-
.../hawkbit/ddi/json/model/DdiConfig.java | 5 -
.../ddi/json/model/DdiConfirmationBase.java | 3 +-
.../json/model/DdiConfirmationBaseAction.java | 24 +--
.../ddi/json/model/DdiControllerBase.java | 2 +-
.../hawkbit/ddi/json/model/DdiDeployment.java | 4 +-
.../ddi/json/model/DdiDeploymentBase.java | 24 +--
.../hawkbit/ddi/json/model/DdiResult.java | 2 +-
.../hawkbit/ddi/json/model/DdiUpdateMode.java | 3 +-
.../rest/api/DdiRootControllerRestApi.java | 27 ++-
.../ddi/json/model/DdiActionHistoryTest.java | 2 +-
.../ddi/json/model/DdiArtifactHashTest.java | 2 +-
.../ddi/json/model/DdiArtifactTest.java | 2 +-
.../json/model/DdiCancelActionToStopTest.java | 2 +-
.../hawkbit/ddi/json/model/DdiCancelTest.java | 2 +-
.../hawkbit/ddi/json/model/DdiChunkTest.java | 2 +-
.../hawkbit/ddi/json/model/DdiConfigTest.java | 2 +-
.../json/model/DdiConfirmationBaseTest.java | 2 +-
.../ddi/json/model/DdiControllerBaseTest.java | 2 +-
.../ddi/json/model/DdiDeploymentBaseTest.java | 2 +-
.../ddi/json/model/DdiDeploymentTest.java | 2 +-
.../ddi/json/model/DdiMetadataTest.java | 2 +-
.../ddi/json/model/DdiPollingTest.java | 2 +-
.../ddi/json/model/DdiProgressTest.java | 2 +-
.../hawkbit/ddi/json/model/DdiResultTest.java | 2 +-
.../hawkbit/ddi/json/model/DdiStatusTest.java | 2 +-
.../rest/resource/DataConversionHelper.java | 25 +--
.../rest/resource/DdiApiConfiguration.java | 5 +-
.../ddi/rest/resource/DdiRootController.java | 40 ++--
.../AbstractDDiApiIntegrationTest.java | 20 +-
.../resource/DdiArtifactDownloadTest.java | 45 ++--
.../rest/resource/DdiCancelActionTest.java | 42 ++--
.../ddi/rest/resource/DdiConfigDataTest.java | 8 +-
.../resource/DdiConfirmationBaseTest.java | 13 +-
.../rest/resource/DdiDeploymentBaseTest.java | 76 +++----
.../rest/resource/DdiInstalledBaseTest.java | 4 +-
.../rest/resource/DdiRootControllerTest.java | 44 ++--
hawkbit-ddi/hawkbit-ddi-starter/pom.xml | 2 +-
.../amqp/AmqpMessageDispatcherService.java | 42 ++--
.../hawkbit/amqp/DmfApiConfiguration.java | 9 +-
.../amqp/RequeueExceptionStrategy.java | 3 +-
.../AmqpMessageDispatcherServiceTest.java | 43 ++--
.../AbstractAmqpServiceIntegrationTest.java | 9 +-
...ssageDispatcherServiceIntegrationTest.java | 5 +-
...pMessageHandlerServiceIntegrationTest.java | 2 +-
.../MgmtDownloadArtifactResource.java | 9 +-
.../rest/resource/MgmtRolloutResource.java | 11 +-
.../resource/MgmtSoftwareModuleResource.java | 24 ++-
.../MgmtSystemManagementResource.java | 4 +-
.../rest/resource/MgmtTargetResource.java | 33 +--
.../mapper/MgmtSoftwareModuleMapper.java | 19 +-
.../resource/mapper/MgmtTargetMapper.java | 4 +-
.../MgmtDistributionSetResourceTest.java | 29 ++-
.../resource/MgmtRolloutResourceTest.java | 61 +++---
.../MgmtSoftwareModuleResourceTest.java | 79 ++++---
.../rest/resource/MgmtTargetResourceTest.java | 79 ++++---
.../resource/MgmtTargetTypeResourceTest.java | 2 +-
hawkbit-mgmt/hawkbit-mgmt-starter/pom.xml | 2 +-
.../src/main/resources/application.properties | 2 +-
.../repository/ArtifactManagement.java | 20 +-
.../repository/ConfirmationManagement.java | 2 +-
.../repository/ControllerManagement.java | 18 +-
.../repository/DeploymentManagement.java | 4 +-
.../repository/DistributionSetManagement.java | 4 +-
.../DistributionSetTypeManagement.java | 3 -
.../repository/RolloutGroupManagement.java | 20 +-
.../hawkbit/repository/RolloutManagement.java | 20 +-
.../repository/SizeConversionHelper.java | 40 ----
.../SoftwareModuleTypeManagement.java | 7 -
.../hawkbit/repository/SystemManagement.java | 4 +-
.../hawkbit/repository/TargetManagement.java | 148 ++++++-------
.../repository/TargetTypeManagement.java | 9 +-
.../repository/TenantStatsManagement.java | 2 +-
.../autoassign/AutoAssignExecutor.java | 8 +-
.../repository/event/TenantAwareEvent.java | 4 +-
.../event/entity/EntityCreatedEvent.java | 2 +-
.../event/entity/EntityDeletedEvent.java | 2 +-
.../event/entity/EntityIdEvent.java | 7 +-
.../event/entity/EntityUpdatedEvent.java | 2 +-
.../event/remote/AbstractRemoteEvent.java | 2 +
.../DistributionSetTagDeletedEvent.java | 2 +-
.../event/remote/DownloadProgressEvent.java | 10 +-
.../event/remote/MultiActionCancelEvent.java | 8 +-
.../event/remote/MultiActionEvent.java | 2 +
.../event/remote/RemoteIdEvent.java | 12 +-
.../TargetAssignDistributionSetEvent.java | 3 +-
.../event/remote/TargetPollEvent.java | 2 +-
.../remote/entity/AbstractActionEvent.java | 11 +-
.../entity/AbstractRolloutGroupEvent.java | 38 +---
.../DistributionSetTagCreatedEvent.java | 3 +-
.../entity/DistributionSetUpdatedEvent.java | 9 +-
.../service/AbstractServiceRemoteEvent.java | 5 +-
.../AssignmentQuotaExceededException.java | 76 ++-----
...utoConfirmationAlreadyActiveException.java | 27 +--
.../CancelActionNotAllowedException.java | 22 +-
.../ConcurrentModificationException.java | 66 ------
.../exception/DeletedException.java | 6 +-
...DistributionSetTypeUndefinedException.java | 20 +-
.../EntityAlreadyExistsException.java | 25 +--
.../exception/EntityNotFoundException.java | 47 +---
.../exception/EntityReadOnlyException.java | 27 +--
.../ForceQuitActionNotAllowedException.java | 22 +-
.../IncompatibleTargetTypeException.java | 42 +---
.../IncompleteDistributionSetException.java | 14 +-
.../InsufficientPermissionException.java | 2 +-
.../InvalidAutoAssignActionTypeException.java | 5 +-
.../InvalidConfirmationFeedbackException.java | 15 +-
.../InvalidDistributionSetException.java | 15 +-
.../exception/InvalidMD5HashException.java | 52 -----
.../InvalidMaintenanceScheduleException.java | 39 +---
...tion.java => InvalidMd5HashException.java} | 21 +-
...ion.java => InvalidSha1HashException.java} | 28 +--
...n.java => InvalidSha256HashException.java} | 28 +--
.../InvalidTargetAttributeException.java | 5 +-
...nvalidTenantConfigurationKeyException.java | 2 +-
.../repository/exception/LockedException.java | 14 +-
.../MethodNotSupportedException.java | 56 -----
.../MultiAssignmentIsNotEnabledException.java | 33 +--
...rovidedInMultiAssignmentModeException.java | 33 +--
.../RSQLParameterSyntaxException.java | 31 +--
...SQLParameterUnsupportedFieldException.java | 33 +--
.../RolloutIllegalStateException.java | 39 +---
...areModuleNotAssignedToTargetException.java | 12 +-
...TypeNotInDistributionSetTypeException.java | 13 +-
.../exception/StopRolloutException.java | 28 +--
.../exception/TargetTypeInUseException.java | 36 +---
.../TargetTypeKeyOrNameRequiredException.java | 8 +-
...TenantConfigurationValidatorException.java | 7 +-
...urationValueChangeNotAllowedException.java | 19 +-
.../exception/TenantNotExistException.java | 35 +--
...ModuleForThisDistributionSetException.java | 18 --
.../hawkbit/repository/model/Action.java | 95 ++++----
.../model/ActionCancellationType.java | 17 +-
.../repository/model/ActionProperties.java | 7 +-
.../repository/model/ActionStatus.java | 5 +-
.../hawkbit/repository/model/Artifact.java | 10 +-
.../repository/model/ArtifactUpload.java | 75 +------
.../model/AssignedSoftwareModule.java | 47 ----
.../model/AutoConfirmationStatus.java | 11 +-
.../hawkbit/repository/model/BaseEntity.java | 3 +-
.../repository/model/DeploymentRequest.java | 68 +++---
.../repository/model/DistributionSet.java | 25 +--
.../DistributionSetInvalidationCount.java | 20 +-
.../repository/model/DistributionSetTag.java | 4 +-
.../hawkbit/repository/model/PollStatus.java | 19 +-
.../model/RepositoryModelConstants.java | 10 +-
.../hawkbit/repository/model/Rollout.java | 33 ++-
.../repository/model/RolloutGroup.java | 53 ++---
.../model/RolloutGroupsValidation.java | 35 +--
.../repository/model/SoftwareModuleType.java | 6 +-
.../hawkbit/repository/model/Statistic.java | 2 +-
.../eclipse/hawkbit/repository/model/Tag.java | 3 +-
.../hawkbit/repository/model/Target.java | 7 -
.../repository/model/TargetFilterQuery.java | 4 +-
.../hawkbit/repository/model/TargetTag.java | 4 +-
.../hawkbit/repository/model/TargetType.java | 8 +-
.../repository/model/TargetUpdateStatus.java | 2 +-
.../model/TargetWithActionType.java | 52 +----
.../model/TenantAwareBaseEntity.java | 2 +-
.../repository/model/TenantConfiguration.java | 3 +-
.../model/TenantConfigurationValue.java | 1 +
.../model/TotalTargetCountActionStatus.java | 2 +-
.../model/TotalTargetCountStatus.java | 52 ++---
.../report}/SystemUsageReport.java | 18 +-
.../report}/SystemUsageReportWithTenants.java | 22 +-
.../model => model/report}/TenantUsage.java | 16 +-
.../hawkbit/utils/TenantConfigHelper.java | 11 +-
.../model/TotalTargetCountStatusTest.java | 2 +-
.../event/EventPublisherConfiguration.java | 3 -
.../repository/RolloutStatusCache.java | 6 +-
.../repository/jpa/utils/ExceptionMapper.java | 2 -
.../jpa/model/AbstractJpaBaseEntity.java | 2 +-
.../jpa/model/AbstractJpaBaseEntity.java | 2 +-
.../jpa/EncryptionAwareDbArtifact.java | 66 ------
.../jpa/JpaRepositoryConfiguration.java | 12 +-
.../jpa/management/JpaArtifactManagement.java | 110 +++++-----
.../management/JpaConfirmationManagement.java | 2 +-
.../management/JpaControllerManagement.java | 44 ++--
.../management/JpaDeploymentManagement.java | 4 +-
.../JpaDistributionSetManagement.java | 10 +-
.../JpaDistributionSetTypeManagement.java | 5 -
.../management/JpaRolloutGroupManagement.java | 46 ++--
.../jpa/management/JpaRolloutManagement.java | 25 ++-
.../JpaSoftwareModuleManagement.java | 95 ++++----
.../JpaSoftwareModuleTypeManagement.java | 5 -
.../jpa/management/JpaSystemManagement.java | 16 +-
.../jpa/management/JpaTargetManagement.java | 36 ++--
.../management/JpaTargetTypeManagement.java | 13 +-
.../management/JpaTenantStatsManagement.java | 2 +-
.../repository/jpa/model/JpaArtifact.java | 8 -
.../condition/PauseRolloutGroupAction.java | 3 +-
.../DistributionSetTypeSpecification.java | 12 --
.../TargetTypeSpecification.java | 2 -
...izeAndStorageQuotaCheckingInputStream.java | 6 +-
.../jpa/AbstractJpaIntegrationTest.java | 6 -
.../DistributionSetAccessControllerTest.java | 12 +-
.../jpa/acm/TargetAccessControllerTest.java | 7 +-
.../acm/TargetTypeAccessControllerTest.java | 4 -
.../autoassign/AutoAssignCheckerIntTest.java | 4 +-
.../management/ArtifactManagementTest.java | 123 +++++------
.../management/ControllerManagementTest.java | 72 ++-----
.../management/DeploymentManagementTest.java | 106 +++++----
...ributionSetInvalidationManagementTest.java | 6 +-
.../DistributionSetManagementTest.java | 29 +--
.../RolloutGroupManagementTest.java | 10 +-
.../jpa/management/RolloutManagementTest.java | 98 +++------
.../SoftwareModuleManagementTest.java | 35 +--
.../SoftwareModuleTypeManagementTest.java | 14 --
.../jpa/management/SystemManagementTest.java | 20 +-
.../TargetManagementSearchTest.java | 4 +-
.../jpa/management/TargetManagementTest.java | 26 +--
.../management/TargetTagManagementTest.java | 6 +-
.../model/EntityInterceptorListenerTest.java | 2 +-
.../jpa/rsql/RsqlRolloutFieldTest.java | 2 +-
.../jpa/rsql/RsqlRolloutGroupFieldTest.java | 4 +-
.../hawkbit-repository-test/pom.xml | 2 +-
.../repository/test/TestConfiguration.java | 23 +-
.../test/util/AbstractIntegrationTest.java | 15 +-
.../util/CleanupTestExecutionListener.java | 2 +-
.../repository/test/util/TestdataFactory.java | 16 +-
.../hawkbit-test-defaults.properties | 3 +-
.../MultiPartFileUploadException.java | 2 +-
.../util/FileStreamingFailedException.java | 4 +-
.../hawkbit/rest/util/FileStreamingUtil.java | 116 +++++-----
.../rest/util/FileStreamingUtilTest.java | 45 +---
.../InMemoryUserAuthoritiesResolver.java | 3 +-
.../eclipse/hawkbit/security/MdcHandler.java | 2 +-
.../security/SpringSecurityAuditorAware.java | 2 +-
hawkbit-test-report/pom.xml | 2 +-
site/content/apis/dmf_api.md | 12 +-
site/content/guides/clustering.md | 2 +-
305 files changed, 2253 insertions(+), 4566 deletions(-)
create mode 100644 hawkbit-artifact/README.md
rename hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/{repository/artifact/AbstractArtifactRepository.java => artifact/AbstractArtifactStorage.java} (71%)
rename hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/{repository/artifact/ArtifactRepository.java => artifact/ArtifactStorage.java} (74%)
rename hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/{repository => }/artifact/encryption/ArtifactEncryption.java (90%)
rename hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/{repository/artifact/encryption/ArtifactEncryptionSecretsStore.java => artifact/encryption/ArtifactEncryptionSecretsStorage.java} (64%)
rename hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/{repository => }/artifact/encryption/ArtifactEncryptionService.java (92%)
rename hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/{repository => }/artifact/exception/ArtifactBinaryNoLongerExistsException.java (62%)
rename hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/{repository => }/artifact/exception/ArtifactBinaryNotFoundException.java (56%)
rename hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/{repository => }/artifact/exception/ArtifactDeleteFailedException.java (62%)
rename hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/{repository => }/artifact/exception/ArtifactEncryptionFailedException.java (73%)
rename hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/{repository => }/artifact/exception/ArtifactEncryptionUnsupportedException.java (73%)
rename hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/{repository => }/artifact/exception/ArtifactStoreException.java (73%)
rename hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/{repository => }/artifact/exception/ArtifactUploadFailedException.java (51%)
rename {hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository => hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact}/exception/FileSizeQuotaExceededException.java (63%)
rename hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/{repository => }/artifact/exception/HashNotMatchException.java (78%)
rename {hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository => hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact}/exception/StorageQuotaExceededException.java (57%)
create mode 100644 hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/model/ArtifactHashes.java
create mode 100644 hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/model/ArtifactStream.java
rename hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/{repository/artifact/model/AbstractDbArtifact.java => artifact/model/StoredArtifactInfo.java} (55%)
create mode 100644 hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/urlresolver/ArtifactUrl.java
create mode 100644 hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/urlresolver/ArtifactUrlResolver.java
create mode 100644 hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/urlresolver/PropertyBasedArtifactUrlResolver.java
rename hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/{repository/artifact/urlhandler/ArtifactUrlHandlerProperties.java => artifact/urlresolver/PropertyBasedArtifactUrlResolverProperties.java} (76%)
delete mode 100644 hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/model/DbArtifact.java
delete mode 100644 hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/model/DbArtifactHash.java
delete mode 100644 hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/ApiType.java
delete mode 100644 hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/ArtifactUrl.java
delete mode 100644 hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/ArtifactUrlHandler.java
delete mode 100644 hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/Base62Util.java
delete mode 100644 hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/PropertyBasedArtifactUrlHandler.java
delete mode 100644 hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/URLPlaceholder.java
rename hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/{repository => }/artifact/encryption/ArtifactEncryptionServiceTest.java (90%)
rename hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/{repository/artifact/urlhandler/PropertyBasedArtifactUrlHandlerTest.java => artifact/urlresolver/PropertyBasedArtifactUrlResolverTest.java} (50%)
delete mode 100644 hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/repository/artifact/urlhandler/Base62UtilTest.java
delete mode 100644 hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/repository/artifact/urlhandler/URLPlaceholderTest.java
create mode 100644 hawkbit-artifact/hawkbit-artifact-fs/README.md
rename hawkbit-artifact/{hawkbit-artifact-repository-filesystem => hawkbit-artifact-fs}/pom.xml (95%)
rename hawkbit-artifact/{hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/repository/artifact/ArtifactFilesystemProperties.java => hawkbit-artifact-fs/src/main/java/org/eclipse/hawkbit/artifact/fs/FileArtifactProperties.java} (88%)
create mode 100644 hawkbit-artifact/hawkbit-artifact-fs/src/main/java/org/eclipse/hawkbit/artifact/fs/FileArtifactStorage.java
rename hawkbit-artifact/{hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/autoconfigure/repository/artifact/filesystem/ArtifactFilesystemConfiguration.java => hawkbit-artifact-fs/src/main/java/org/eclipse/hawkbit/autoconfigure/artifact/fs/FileArtifactStorageConfiguration.java} (50%)
create mode 100644 hawkbit-artifact/hawkbit-artifact-fs/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
rename hawkbit-artifact/{hawkbit-artifact-repository-filesystem/src/test/java/org/eclipse/hawkbit/repository/artifact/ArtifactFilesystemRepositoryTest.java => hawkbit-artifact-fs/src/test/java/org/eclipse/hawkbit/artifact/fs/FileArtifactStorageTest.java} (74%)
delete mode 100644 hawkbit-artifact/hawkbit-artifact-repository-filesystem/README.md
delete mode 100644 hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/repository/artifact/ArtifactFileNotFoundException.java
delete mode 100644 hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/repository/artifact/ArtifactFilesystem.java
delete mode 100644 hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/repository/artifact/ArtifactFilesystemRepository.java
delete mode 100644 hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
delete mode 100644 hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/test/java/org/eclipse/hawkbit/repository/artifact/ArtifactFilesystemTest.java
rename hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/artifact/{ArtifactUrlHandlerAutoConfiguration.java => PropertyBasedArtifactUrlResolverAutoConfiguration.java} (52%)
rename hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/scheduling/{AsyncConfigurerThreadPoolProperties.java => ExecutorProperties.java} (92%)
rename hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/{InMemoryUserManagementAutoConfiguration.java => StaticUserManagementAutoConfiguration.java} (89%)
rename hawkbit-core/src/main/java/org/eclipse/hawkbit/{ => tenancy}/cache/TenantAwareCacheManager.java (87%)
rename hawkbit-core/src/main/java/org/eclipse/hawkbit/{cache/TenancyCacheManager.java => tenancy/cache/TenantCacheManager.java} (90%)
rename hawkbit-core/src/test/java/org/eclipse/hawkbit/repository/{FileNameFieldsTest.java => RsqlQueryFieldsTest.java} (89%)
delete mode 100644 hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SizeConversionHelper.java
delete mode 100644 hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/ConcurrentModificationException.java
delete mode 100644 hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/InvalidMD5HashException.java
rename hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/{InvalidTargetAddressException.java => InvalidMd5HashException.java} (51%)
rename hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/{InvalidSHA1HashException.java => InvalidSha1HashException.java} (54%)
rename hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/{InvalidSHA256HashException.java => InvalidSha256HashException.java} (54%)
delete mode 100644 hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/MethodNotSupportedException.java
delete mode 100644 hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/AssignedSoftwareModule.java
rename hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/{report/model => model/report}/SystemUsageReport.java (64%)
rename hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/{report/model => model/report}/SystemUsageReportWithTenants.java (65%)
rename hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/{report/model => model/report}/TenantUsage.java (73%)
delete mode 100644 hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/EncryptionAwareDbArtifact.java
diff --git a/.3rd-party/README.md b/.3rd-party/README.md
index f41d19886..94a755e46 100644
--- a/.3rd-party/README.md
+++ b/.3rd-party/README.md
@@ -1,10 +1,16 @@
-# Third-Party Dependencies
+Third-Party Dependencies
+===
-This folder provides listings of all 3rd-party dependencies incl. their licenses. There is a dedicated subfolder for
-each release (and milestone) holding the release-specific information.
+This folder contains DEPENDENCIES that has the list of all 3rd-party dependencies (including transitive) with their licenses and approval status. Each release (and milestone) holds the release-specific information.
-The DEPENDENCIES file could be generated manually using [Eclipse Dash License Tool](https://github.com/eclipse/dash-licenses) maven plugin by running in root folder:
+DEPENDENCIES file is generated (automatically and committed) by [../.github/workflows/reusable_workflow_license-scan.yaml](../.github/workflows/reusable_workflow_license-scan.yaml) during the release process ([../.github/workflows/release.yaml](../.github/workflows/release.yaml)) and on daily basis ([../.github/workflows/license-scan.yaml](../.github/workflows/license-scan.yaml)). It is also
+
+DEPENDENCIES file could be generated manually using [Eclipse Dash License Tool](https://github.com/eclipse/dash-licenses) maven plugin by running:
```shell
-mvn clean install -PcheckLicense -DskipTests \
- --projects '!org.eclipse.hawkbit:hawkbit-repository-test,!org.eclipse.hawkbit:hawkbit-dmf-rabbitmq-test'
+$ cd .. && mvn license-tool:license-check -Ddash.fail=false -PcheckLicense
+```
+
+Note: Some projects (e.g. test artifacts) could be excluded with *--projects* parameter, e.g:
+```shell
+$ cd .. && mvn license-tool:license-check -Ddash.fail=false -PcheckLicense \ --projects '!org.eclipse.hawkbit:hawkbit-repository-test,!org.eclipse.hawkbit:hawkbit-dmf-rabbitmq-test'
```
diff --git a/.github/workflows/reusable_workflow_license-scan.yaml b/.github/workflows/reusable_workflow_license-scan.yaml
index b2fd670e9..23f957dff 100644
--- a/.github/workflows/reusable_workflow_license-scan.yaml
+++ b/.github/workflows/reusable_workflow_license-scan.yaml
@@ -50,7 +50,7 @@ jobs:
- name: Check dependency licenses with dash tool (and open issues to Dash IP lab, doesn't fail)
if: ${{ inputs.open_tickets }}
run: |
- mvn license-tool:license-check -Ddash.fail=false -PcheckLicense -Ddash.iplab.token=${GITLAB_API_TOKEN} --projects '!org.eclipse.hawkbit:hawkbit-repository-test,!org.eclipse.hawkbit:hawkbit-dmf-rabbitmq-test'
+ mvn license-tool:license-check -Ddash.fail=false -PcheckLicense -Ddash.iplab.token=${GITLAB_API_TOKEN}
CHANGED_FILES_COUNT=$(git status --short | wc -l)
CHANGED_FILES_COUNT=${CHANGED_FILES_COUNT//[[:space:]]/}
echo "Number of changed files: ${CHANGED_FILES_COUNT}"
diff --git a/docker/README.md b/docker/README.md
index 028cd49e0..97f9dc6b2 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -1,24 +1,33 @@
hawkBit Docker
===
-# Setup
+## Overview
+This folder contains example Docker build and Docker Compose files to build and start the hawkBit as monolith or as microservices.
-## A: Docker Container
-Start the hawkBit Update Server as a single container (requires Docker to be installed and all dependencies to be available)
+## Build
+You could build the hawkbit Docker images following the [README.md](build/README.md) instructions.
+
+## Start
+You can start hawkbit as a Docker Container (only monolith) or with Docker Compose
+
+#### A: Docker Container (only as monolith)
+_Note: You need to have Docker installed on your machine._
+
+Start the hawkBit Update Server (monolith) as a single container (with embedded H2, if you configure a different database, e.g. MySQL or PostgreSQL, you should start it separately):
```bash
$ docker run -d -p 8080:8080 hawkbit/hawkbit-update-server:latest
```
-## B: Docker Compose
-Start the hawkBit Update Server together with an MySQL and RabbitMQ instance as containers (Requires Docker Compose to be installed)
+### B: Docker Compose
+_Note: You need to have Docker Compose installed on your machine._
+
+Start the hawkBit Update Server (monolith) together with an MySQL and RabbitMQ instance as containers (Requires Docker Compose to be installed)
```bash
$ docker compose -f mysql/docker-compose-monolith-mysql.yml up
```
-You could, also start it in different flavours, with UI or in microservices mode.
-
-Note: Whit the upper command CTRL+C shuts down all services. Add '-d' at the end to start all into detached mode:
+With the upper command CTRL+C shuts down all services. Add '-d' at the end to start all into detached mode:
```bash
$ docker compose -f mysql/docker-compose-monolith-mysql.yml up -d
```
@@ -27,15 +36,20 @@ Then stop all services with:
$ docker compose -f mysql/docker-compose-monolith-mysql.yml down
```
-# Access
-| Service / Container | URL | Login | A | B | C |
-|--------------------------|--------------------------------------------------|-------------|----------|----------|----------|
-| hawkBit Update Server | [http://localhost:8080/](http://localhost:8080/) | admin:admin | ✓ | ✓ | ✓ |
-| MySQL | localhost:3306/hawkbit | root | | ✓ | ✓ |
-| RabbitMQ | [http://localhost:15672](http://localhost:15672) | guest:guest | | ✓ | ✓ |
+You could, also start it in different flavours, with UI or in microservices mode (see Docker Compose files in [mysql](./mysql) and [postgres](./postgres) folders). For instance to start with PostgreSQL, with RabbitMQ, in microservices mode and with UI you could use:
+```bash
+$ docker compose -f postgres/docker-compose-micro-services-with-simple-ui-postgres.yml up
+```
-# Configuration
-You can override application.properties by setting an environment variable SPRING_APPLICATION_JSON for hawkbit container.
+### Access
+| Service / Container | URL | Login | A | B |
+|-----------------------|------------------------|-------------|----------|----------|
+| hawkBit Update Server | [http://localhost:8080/](http://localhost:8080/) | admin:admin | ✓ | ✓ |
+| MySQL | localhost:3306/hawkbit | root | | ✓ |
+| RabbitMQ | [http://localhost:15672](http://localhost:15672) | guest:guest | | ✓ |
+
+### Configuration
+You can override _application.properties_ by setting an environment variable _SPRING_APPLICATION_JSON_ to the hawkbit container, e.g.:
```yaml
hawkbit:
@@ -55,4 +69,4 @@ hawkbit:
"hawkbit.security.user.hawkbit.password": "{noop}isAwesome!",
"hawkbit.security.user.hawkbit.roles": "TENANT_ADMIN"
}'
-```
+```
\ No newline at end of file
diff --git a/docker/build/README.md b/docker/build/README.md
index c3e0cf222..c3094e1e5 100644
--- a/docker/build/README.md
+++ b/docker/build/README.md
@@ -28,9 +28,7 @@ Docker image could be build, for example, with (fixed version 0.4.1 is just an e
```shell
docker build --build-arg HAWKBIT_APP=hawkbit-update-server --build-arg HAWKBIT_VERSION=0.4.1 -t hawkbit_update_server:0.4.1 . -f Dockerfile
```
-
or just by:
-
```shell
docker build --build-arg HAWKBIT_VERSION=0.4.1 -t hawkbit_update_server:0.4.1 .
```
diff --git a/hawkbit-artifact/README.md b/hawkbit-artifact/README.md
new file mode 100644
index 000000000..b1b67d257
--- /dev/null
+++ b/hawkbit-artifact/README.md
@@ -0,0 +1,5 @@
+hawkBit Artifact
+===
+The module contains internal modules for artifact storage and encryption:
+* [hawkbit-artifact-api](hawkbit-artifact-api/README.md) - see for artifact API module
+* [hawkbit-artifact-fs](hawkbit-artifact-fs/README.md) - see for file-system based artifact storage implementation
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/README.md b/hawkbit-artifact/hawkbit-artifact-api/README.md
index 49dc970ba..623245991 100644
--- a/hawkbit-artifact/hawkbit-artifact-api/README.md
+++ b/hawkbit-artifact/hawkbit-artifact-api/README.md
@@ -1,3 +1,6 @@
-# hawkBit Artifact API
-
-Various internal interfaces artifact API classes.
\ No newline at end of file
+hawkBit Artifact API
+===
+The module contains artifact API classes supporting following main concepts:
+* Artifact Storage - represented by the [ArtifactStorage](src/main/java/org/eclipse/hawkbit/artifact/ArtifactStorage.java) interface. It serves for artifact binary store operations
+* Artifact Encryption - represented by the [ArtifactEncryptionService](src/main/java/org/eclipse/hawkbit/artifact/encryption/ArtifactEncryptionService.java). It is a pluggable implementation of artifact encryption operations.
+* Artifact URL handling - represented by[ArtifactUrlResolver](src/main/java/org/eclipse/hawkbit/artifact/urlresolver/ArtifactUrlResolver.java) interface. It provides resolving URLs to the artifacts. The module provides a simple property based implementation ([PropertyBasedArtifactUrlResolver](src/main/java/org/eclipse/hawkbit/artifact/urlresolver/PropertyBasedArtifactUrlResolver.java))
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/AbstractArtifactRepository.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/AbstractArtifactStorage.java
similarity index 71%
rename from hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/AbstractArtifactRepository.java
rename to hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/AbstractArtifactStorage.java
index 966b88df2..1b494e1b6 100644
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/AbstractArtifactRepository.java
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/AbstractArtifactStorage.java
@@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.repository.artifact;
+package org.eclipse.hawkbit.artifact;
import java.io.BufferedOutputStream;
import java.io.File;
@@ -22,17 +22,17 @@ import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
import lombok.extern.slf4j.Slf4j;
-import org.eclipse.hawkbit.repository.artifact.exception.ArtifactStoreException;
-import org.eclipse.hawkbit.repository.artifact.exception.HashNotMatchException;
-import org.eclipse.hawkbit.repository.artifact.model.AbstractDbArtifact;
-import org.eclipse.hawkbit.repository.artifact.model.DbArtifactHash;
+import org.eclipse.hawkbit.artifact.exception.ArtifactStoreException;
+import org.eclipse.hawkbit.artifact.exception.HashNotMatchException;
+import org.eclipse.hawkbit.artifact.model.ArtifactHashes;
+import org.eclipse.hawkbit.artifact.model.StoredArtifactInfo;
import org.springframework.util.ObjectUtils;
/**
* Abstract utility class for ArtifactRepository implementations with common functionality, e.g. computation of hashes.
*/
@Slf4j
-public abstract class AbstractArtifactRepository implements ArtifactRepository {
+public abstract class AbstractArtifactStorage implements ArtifactStorage {
private static final String TEMP_FILE_PREFIX = "tmp";
private static final String TEMP_FILE_SUFFIX = "artifactrepo";
@@ -40,9 +40,9 @@ public abstract class AbstractArtifactRepository implements ArtifactRepository {
// suppress warning, of not strong enough hashing algorithm, SHA-1 and MD5 is not used security related
@SuppressWarnings("squid:S2070")
@Override
- public AbstractDbArtifact store(
+ public StoredArtifactInfo store(
final String tenant, final InputStream content, final String filename, final String contentType,
- final DbArtifactHash providedHashes) {
+ final ArtifactHashes providedHashes) {
final MessageDigest mdSHA1;
final MessageDigest mdMD5;
final MessageDigest mdSHA256;
@@ -60,19 +60,19 @@ public abstract class AbstractArtifactRepository implements ArtifactRepository {
final HexFormat hexFormat = HexFormat.of().withLowerCase();
- final String sha1Hash16 = hexFormat.formatHex(mdSHA1.digest());
- final String md5Hash16 = hexFormat.formatHex(mdMD5.digest());
- final String sha256Hash16 = hexFormat.formatHex(mdSHA256.digest());
+ final String sha1Hash = hexFormat.formatHex(mdSHA1.digest());
+ final String md5Hash = hexFormat.formatHex(mdMD5.digest());
+ final String sha256Hash = hexFormat.formatHex(mdSHA256.digest());
- checkHashes(providedHashes, sha1Hash16, md5Hash16, sha256Hash16);
+ checkHashes(providedHashes, sha1Hash, md5Hash, sha256Hash);
// Check if file with same sha1 hash exists and if so return it
- if (existsBySha1(tenant, sha1Hash16)) {
+ if (existsBySha1(tenant, sha1Hash)) {
// TODO - shall check if the file is really the same as bytes or just sha1 hash is the same
- return addMissingHashes(getBySha1(tenant, sha1Hash16), sha1Hash16, md5Hash16, sha256Hash16);
+ return new StoredArtifactInfo(contentType, tempFile.length(), new ArtifactHashes(sha1Hash, md5Hash, sha256Hash));
}
- return store(sanitizeTenant(tenant), new DbArtifactHash(sha1Hash16, md5Hash16, sha256Hash16), contentType, tempFile);
+ return store(sanitizeTenant(tenant), new ArtifactHashes(sha1Hash, md5Hash, sha256Hash), contentType, tempFile);
} catch (final IOException e) {
throw new ArtifactStoreException(e.getMessage(), e);
} finally {
@@ -104,13 +104,13 @@ public abstract class AbstractArtifactRepository implements ArtifactRepository {
return file.getPath();
}
- protected abstract AbstractDbArtifact store(final String tenant, final DbArtifactHash base16Hashes,
- final String contentType, final String tempFile) throws IOException;
+ protected abstract StoredArtifactInfo store(
+ final String tenant, final ArtifactHashes base16Hashes, final String contentType, final String tempFile) throws IOException;
// java:S1066 - more readable with separate "if" statements
// java:S4042 - delete reason is not needed
@SuppressWarnings({ "java:S1066", "java:S4042" })
- static File createTempFile(final boolean directory) {
+ public static File createTempFile(final boolean directory) {
try {
final File file = (directory
? Files.createTempDirectory(TEMP_FILE_PREFIX)
@@ -138,22 +138,22 @@ public abstract class AbstractArtifactRepository implements ArtifactRepository {
}
}
- private static void checkHashes(final DbArtifactHash providedHashes,
- final String sha1Hash16, final String md5Hash16, final String sha256Hash16) {
+ private static void checkHashes(
+ final ArtifactHashes providedHashes, final String sha1Hash16, final String md5Hash16, final String sha256Hash16) {
if (providedHashes == null) {
return;
}
- if (areHashesNotMatching(providedHashes.getSha1(), sha1Hash16)) {
- throw new HashNotMatchException("The given sha1 hash " + providedHashes.getSha1() +
+ if (areHashesNotMatching(providedHashes.sha1(), sha1Hash16)) {
+ throw new HashNotMatchException("The given sha1 hash " + providedHashes.sha1() +
" does not match the calculated sha1 hash " + sha1Hash16, HashNotMatchException.SHA1);
}
- if (areHashesNotMatching(providedHashes.getMd5(), md5Hash16)) {
- throw new HashNotMatchException("The given md5 hash " + providedHashes.getMd5() +
+ if (areHashesNotMatching(providedHashes.md5(), md5Hash16)) {
+ throw new HashNotMatchException("The given md5 hash " + providedHashes.md5() +
" does not match the calculated md5 hash " + md5Hash16, HashNotMatchException.MD5);
}
- if (areHashesNotMatching(providedHashes.getSha256(), sha256Hash16)) {
- throw new HashNotMatchException("The given sha256 hash " + providedHashes.getSha256() +
+ if (areHashesNotMatching(providedHashes.sha256(), sha256Hash16)) {
+ throw new HashNotMatchException("The given sha256 hash " + providedHashes.sha256() +
" does not match the calculated sha256 hash " + sha256Hash16, HashNotMatchException.SHA256);
}
}
@@ -167,16 +167,6 @@ public abstract class AbstractArtifactRepository implements ArtifactRepository {
return new DigestInputStream(new DigestInputStream(new DigestInputStream(input, mdSHA256), mdMD5), mdSHA1);
}
- private AbstractDbArtifact addMissingHashes(final AbstractDbArtifact existing,
- final String calculatedSha1, final String calculatedMd5, final String calculatedSha256) {
- final String sha1 = checkEmpty(existing.getHashes().getSha1(), calculatedSha1);
- final String md5 = checkEmpty(existing.getHashes().getMd5(), calculatedMd5);
- final String sha256 = checkEmpty(existing.getHashes().getSha256(), calculatedSha256);
-
- existing.setHashes(new DbArtifactHash(sha1, md5, sha256));
- return existing;
- }
-
private String checkEmpty(final String value, final String fallback) {
return ObjectUtils.isEmpty(value) ? fallback : value;
}
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/ArtifactRepository.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/ArtifactStorage.java
similarity index 74%
rename from hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/ArtifactRepository.java
rename to hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/ArtifactStorage.java
index b322cb465..b1c7e53c1 100644
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/ArtifactRepository.java
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/ArtifactStorage.java
@@ -7,22 +7,23 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.repository.artifact;
+package org.eclipse.hawkbit.artifact;
import java.io.InputStream;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
-import org.eclipse.hawkbit.repository.artifact.exception.ArtifactStoreException;
-import org.eclipse.hawkbit.repository.artifact.exception.HashNotMatchException;
-import org.eclipse.hawkbit.repository.artifact.model.AbstractDbArtifact;
-import org.eclipse.hawkbit.repository.artifact.model.DbArtifactHash;
+import org.eclipse.hawkbit.artifact.exception.ArtifactBinaryNotFoundException;
+import org.eclipse.hawkbit.artifact.exception.ArtifactStoreException;
+import org.eclipse.hawkbit.artifact.exception.HashNotMatchException;
+import org.eclipse.hawkbit.artifact.model.ArtifactHashes;
+import org.eclipse.hawkbit.artifact.model.StoredArtifactInfo;
/**
- * ArtifactRepository service interface.
+ * Artifact Store service interface.
*/
-public interface ArtifactRepository {
+public interface ArtifactStorage {
/**
* Stores an artifact into the repository.
@@ -37,20 +38,20 @@ public interface ArtifactRepository {
* @throws ArtifactStoreException in case storing of the artifact was not successful
* @throws HashNotMatchException in case {@code hash} is provided and not matching to the calculated hashes during storing
*/
- AbstractDbArtifact store(
+ StoredArtifactInfo store(
@NotEmpty String tenant, @NotNull InputStream content, @NotEmpty String filename,
- String contentType, DbArtifactHash hash);
+ String contentType, ArtifactHashes hash);
/**
- * Retrieves a {@link AbstractDbArtifact} from the store by its SHA1 hash. Throws {@link org.eclipse.hawkbit.repository.artifact.exception.ArtifactBinaryNotFoundException} if not found.
+ * Retrieves a {@link StoredArtifactInfo} from the store by its SHA1 hash. Throws {@link ArtifactBinaryNotFoundException} if not found.
+ * The caller is responsible to close the InputStream.
*
* @param tenant the tenant to store the artifact
* @param sha1Hash the sha1-hash of the file to lookup.
* @return The artifact file object or {@code null} if no file exists.
* @throws UnsupportedOperationException if implementation does not support the operation
*/
- AbstractDbArtifact getBySha1(@NotEmpty String tenant, @NotEmpty String sha1Hash);
-
+ InputStream getBySha1(@NotEmpty String tenant, @NotEmpty String sha1Hash);
/**
* Checks if an artifact exists for a given tenant by its sha1 hash
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/encryption/ArtifactEncryption.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/encryption/ArtifactEncryption.java
similarity index 90%
rename from hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/encryption/ArtifactEncryption.java
rename to hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/encryption/ArtifactEncryption.java
index 8d576ab56..023ba301f 100644
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/encryption/ArtifactEncryption.java
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/encryption/ArtifactEncryption.java
@@ -7,13 +7,13 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.repository.artifact.encryption;
+package org.eclipse.hawkbit.artifact.encryption;
import java.io.InputStream;
import java.util.Map;
import java.util.Set;
-import org.eclipse.hawkbit.repository.artifact.exception.ArtifactEncryptionFailedException;
+import org.eclipse.hawkbit.artifact.exception.ArtifactEncryptionFailedException;
/**
* Interface definition for artifact encryption.
@@ -23,7 +23,7 @@ public interface ArtifactEncryption {
/**
* Defines the required secret keys for particular encryption algorithm.
*
- * @return list of required secret keys
+ * @return set of required secret keys
*/
Set requiredSecretKeys();
@@ -61,4 +61,4 @@ public interface ArtifactEncryption {
* @return encryption overhead in byte
*/
int encryptionSizeOverhead();
-}
+}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/encryption/ArtifactEncryptionSecretsStore.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/encryption/ArtifactEncryptionSecretsStorage.java
similarity index 64%
rename from hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/encryption/ArtifactEncryptionSecretsStore.java
rename to hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/encryption/ArtifactEncryptionSecretsStorage.java
index c1e62572a..c347e796b 100644
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/encryption/ArtifactEncryptionSecretsStore.java
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/encryption/ArtifactEncryptionSecretsStorage.java
@@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.repository.artifact.encryption;
+package org.eclipse.hawkbit.artifact.encryption;
import java.util.Optional;
@@ -15,7 +15,7 @@ import java.util.Optional;
* Interface definition for artifact encryption secrets store. It maintains secret key/value pairs
* identified by id (e.g. software module id)
*/
-public interface ArtifactEncryptionSecretsStore {
+public interface ArtifactEncryptionSecretsStorage {
/**
* Adds secret key/value pair associated with particular id (e.g. software module id) to the store.
@@ -26,14 +26,6 @@ public interface ArtifactEncryptionSecretsStore {
*/
void addSecret(final long id, final String secretKey, final String secretValue);
- /**
- * Checks if secret is present for particular id and key in the store.
- *
- * @param id id of the secret
- * @param secretKey key of the secret
- */
- boolean secretExists(final long id, final String secretKey);
-
/**
* Retrieves secret value associated with particular id and key from the store.
*
@@ -41,12 +33,4 @@ public interface ArtifactEncryptionSecretsStore {
* @param secretKey key of the secret
*/
Optional getSecret(final long id, final String secretKey);
-
- /**
- * Removes secret key/value pair associated with particular id from the store.
- *
- * @param id id of the secret
- * @param secretKey key of the secret
- */
- void removeSecret(final long id, final String secretKey);
}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/encryption/ArtifactEncryptionService.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/encryption/ArtifactEncryptionService.java
similarity index 92%
rename from hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/encryption/ArtifactEncryptionService.java
rename to hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/encryption/ArtifactEncryptionService.java
index 5e76fe06c..22f5fb3b8 100644
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/encryption/ArtifactEncryptionService.java
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/encryption/ArtifactEncryptionService.java
@@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.repository.artifact.encryption;
+package org.eclipse.hawkbit.artifact.encryption;
import java.io.InputStream;
import java.util.HashMap;
@@ -17,12 +17,11 @@ import java.util.Set;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
-import org.eclipse.hawkbit.repository.artifact.exception.ArtifactEncryptionUnsupportedException;
+import org.eclipse.hawkbit.artifact.exception.ArtifactEncryptionUnsupportedException;
import org.springframework.beans.factory.annotation.Autowired;
/**
- * Service responsible for encryption operations. Should be registered as a bean in order its autowired dependencies
- * to be injected.
+ * Service responsible for encryption operations. Should be registered as a bean in order its autowired dependencies to be injected.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings("java:S6548") // singleton holder ensures static access to spring resources in some places
@@ -31,7 +30,7 @@ public final class ArtifactEncryptionService {
private static final ArtifactEncryptionService SINGLETON = new ArtifactEncryptionService();
private ArtifactEncryption artifactEncryption;
- private ArtifactEncryptionSecretsStore artifactEncryptionSecretsStore;
+ private ArtifactEncryptionSecretsStorage artifactEncryptionSecretsStore;
/**
* @return the artifact encryption service singleton instance
@@ -46,7 +45,7 @@ public final class ArtifactEncryptionService {
}
@Autowired(required = false) // spring setter injection
- public void setArtifactEncryptionSecretsStore(final ArtifactEncryptionSecretsStore artifactEncryptionSecretsStore) {
+ public void setArtifactEncryptionSecretsStore(final ArtifactEncryptionSecretsStorage artifactEncryptionSecretsStore) {
this.artifactEncryptionSecretsStore = artifactEncryptionSecretsStore;
}
@@ -111,7 +110,7 @@ public final class ArtifactEncryptionService {
throw new ArtifactEncryptionUnsupportedException("Artifact decryption is not supported.");
}
- final var secrets = getEncryptionSecrets(id);
+ final Map secrets = getEncryptionSecrets(id);
try {
return artifactEncryption.decryptStream(secrets, encryptedArtifactStream);
} finally {
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/ArtifactBinaryNoLongerExistsException.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/ArtifactBinaryNoLongerExistsException.java
similarity index 62%
rename from hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/ArtifactBinaryNoLongerExistsException.java
rename to hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/ArtifactBinaryNoLongerExistsException.java
index e85e6f577..8eaca6311 100644
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/ArtifactBinaryNoLongerExistsException.java
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/ArtifactBinaryNoLongerExistsException.java
@@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.repository.artifact.exception;
+package org.eclipse.hawkbit.artifact.exception;
import java.io.Serial;
@@ -28,29 +28,7 @@ public class ArtifactBinaryNoLongerExistsException extends AbstractServerRtExcep
private static final SpServerError THIS_ERROR = SpServerError.SP_ARTIFACT_BINARY_DELETED;
- /**
- * Creates a new ArtifactBinaryGoneException error.
- */
public ArtifactBinaryNoLongerExistsException() {
super(THIS_ERROR);
}
-
- /**
- * Creates a new ArtifactBinaryGoneException error with cause.
- *
- * @param cause for the exception
- */
- public ArtifactBinaryNoLongerExistsException(final Throwable cause) {
- super(THIS_ERROR, cause);
- }
-
- /**
- * Creates a new ArtifactBinaryGoneException error with message.
- *
- * @param message of the error
- */
- public ArtifactBinaryNoLongerExistsException(final String message) {
- super(message, THIS_ERROR);
- }
-
-}
+}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/ArtifactBinaryNotFoundException.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/ArtifactBinaryNotFoundException.java
similarity index 56%
rename from hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/ArtifactBinaryNotFoundException.java
rename to hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/ArtifactBinaryNotFoundException.java
index 5179a8e54..18e8021cc 100644
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/ArtifactBinaryNotFoundException.java
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/ArtifactBinaryNotFoundException.java
@@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.repository.artifact.exception;
+package org.eclipse.hawkbit.artifact.exception;
import java.io.Serial;
@@ -23,25 +23,7 @@ public final class ArtifactBinaryNotFoundException extends AbstractServerRtExcep
@Serial
private static final long serialVersionUID = 1L;
- /**
- * Creates a new FileUploadFailedException with
- * {@link SpServerError#SP_ARTIFACT_LOAD_FAILED} error.
- */
- public ArtifactBinaryNotFoundException() {
- super(SpServerError.SP_ARTIFACT_LOAD_FAILED);
- }
-
- /**
- * @param cause for the exception
- */
- public ArtifactBinaryNotFoundException(final Throwable cause) {
- super(SpServerError.SP_ARTIFACT_LOAD_FAILED, cause);
- }
-
- /**
- * @param message of the error
- */
public ArtifactBinaryNotFoundException(final String message) {
- super(message, SpServerError.SP_ARTIFACT_LOAD_FAILED);
+ super(SpServerError.SP_ARTIFACT_LOAD_FAILED, message);
}
-}
+}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/ArtifactDeleteFailedException.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/ArtifactDeleteFailedException.java
similarity index 62%
rename from hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/ArtifactDeleteFailedException.java
rename to hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/ArtifactDeleteFailedException.java
index 0f7e05418..05b5dcfe8 100644
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/ArtifactDeleteFailedException.java
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/ArtifactDeleteFailedException.java
@@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.repository.artifact.exception;
+package org.eclipse.hawkbit.artifact.exception;
import java.io.Serial;
@@ -26,25 +26,7 @@ public final class ArtifactDeleteFailedException extends AbstractServerRtExcepti
@Serial
private static final long serialVersionUID = 1L;
- /**
- * Creates a new FileUploadFailedException with
- * {@link SpServerError#SP_ARTIFACT_DELETE_FAILED} error.
- */
- public ArtifactDeleteFailedException() {
- super(SpServerError.SP_ARTIFACT_DELETE_FAILED);
- }
-
- /**
- * @param cause for the exception
- */
public ArtifactDeleteFailedException(final Throwable cause) {
super(SpServerError.SP_ARTIFACT_DELETE_FAILED, cause);
}
-
- /**
- * @param message of the error
- */
- public ArtifactDeleteFailedException(final String message) {
- super(message, SpServerError.SP_ARTIFACT_DELETE_FAILED);
- }
-}
+}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/ArtifactEncryptionFailedException.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/ArtifactEncryptionFailedException.java
similarity index 73%
rename from hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/ArtifactEncryptionFailedException.java
rename to hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/ArtifactEncryptionFailedException.java
index edc8150ee..d5d025542 100644
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/ArtifactEncryptionFailedException.java
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/ArtifactEncryptionFailedException.java
@@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.repository.artifact.exception;
+package org.eclipse.hawkbit.artifact.exception;
import java.io.Serial;
@@ -35,16 +35,8 @@ public final class ArtifactEncryptionFailedException extends AbstractServerRtExc
@Getter
private final EncryptionOperation encryptionOperation;
- public ArtifactEncryptionFailedException(final EncryptionOperation encryptionOperation) {
- this(encryptionOperation, null, null);
- }
-
- public ArtifactEncryptionFailedException(final EncryptionOperation encryptionOperation, final String message) {
- this(encryptionOperation, message, null);
- }
-
public ArtifactEncryptionFailedException(final EncryptionOperation encryptionOperation, final String message, final Throwable cause) {
- super(message, SpServerError.SP_ARTIFACT_ENCRYPTION_FAILED, cause);
+ super(SpServerError.SP_ARTIFACT_ENCRYPTION_FAILED, message, cause);
this.encryptionOperation = encryptionOperation;
}
}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/ArtifactEncryptionUnsupportedException.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/ArtifactEncryptionUnsupportedException.java
similarity index 73%
rename from hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/ArtifactEncryptionUnsupportedException.java
rename to hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/ArtifactEncryptionUnsupportedException.java
index ea2c2c76b..8a1656e4a 100644
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/ArtifactEncryptionUnsupportedException.java
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/ArtifactEncryptionUnsupportedException.java
@@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.repository.artifact.exception;
+package org.eclipse.hawkbit.artifact.exception;
import java.io.Serial;
@@ -22,17 +22,7 @@ public final class ArtifactEncryptionUnsupportedException extends AbstractServer
@Serial
private static final long serialVersionUID = 1L;
- /**
- * Constructor.
- */
- public ArtifactEncryptionUnsupportedException() {
- super(SpServerError.SP_ARTIFACT_ENCRYPTION_NOT_SUPPORTED);
- }
-
- /**
- * @param message of the error
- */
public ArtifactEncryptionUnsupportedException(final String message) {
- super(message, SpServerError.SP_ARTIFACT_ENCRYPTION_NOT_SUPPORTED);
+ super(SpServerError.SP_ARTIFACT_ENCRYPTION_NOT_SUPPORTED, message);
}
-}
+}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/ArtifactStoreException.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/ArtifactStoreException.java
similarity index 73%
rename from hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/ArtifactStoreException.java
rename to hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/ArtifactStoreException.java
index 5d4e5dc0e..cf767fdbc 100644
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/ArtifactStoreException.java
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/ArtifactStoreException.java
@@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.repository.artifact.exception;
+package org.eclipse.hawkbit.artifact.exception;
import java.io.Serial;
@@ -19,12 +19,10 @@ public class ArtifactStoreException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;
- /**
- * Constructs a ArtifactStoreException with message and cause.
- *
- * @param message the message of the exception
- * @param cause of the exception
- */
+ public ArtifactStoreException(final String message) {
+ this(message, null);
+ }
+
public ArtifactStoreException(final String message, final Throwable cause) {
super(message, cause);
}
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/ArtifactUploadFailedException.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/ArtifactUploadFailedException.java
similarity index 51%
rename from hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/ArtifactUploadFailedException.java
rename to hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/ArtifactUploadFailedException.java
index 4647b210f..c99ce2bc7 100644
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/ArtifactUploadFailedException.java
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/ArtifactUploadFailedException.java
@@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.repository.artifact.exception;
+package org.eclipse.hawkbit.artifact.exception;
import java.io.Serial;
@@ -23,34 +23,7 @@ public final class ArtifactUploadFailedException extends AbstractServerRtExcepti
@Serial
private static final long serialVersionUID = 1L;
- /**
- * Creates a new FileUploadFailedException with
- * {@link SpServerError#SP_ARTIFACT_UPLOAD_FAILED} error.
- */
- public ArtifactUploadFailedException() {
- super(SpServerError.SP_ARTIFACT_UPLOAD_FAILED);
- }
-
- /**
- * @param cause for the exception
- */
public ArtifactUploadFailedException(final Throwable cause) {
super(SpServerError.SP_ARTIFACT_UPLOAD_FAILED, cause);
}
-
- /**
- * @param message of the error
- */
- public ArtifactUploadFailedException(final String message) {
- super(message, SpServerError.SP_ARTIFACT_UPLOAD_FAILED);
- }
-
- /**
- * @param message for the error
- * @param cause of the error
- */
- public ArtifactUploadFailedException(final String message, final Throwable cause) {
- super(message, SpServerError.SP_ARTIFACT_UPLOAD_FAILED, cause);
- }
-
-}
+}
\ No newline at end of file
diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/FileSizeQuotaExceededException.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/FileSizeQuotaExceededException.java
similarity index 63%
rename from hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/FileSizeQuotaExceededException.java
rename to hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/FileSizeQuotaExceededException.java
index 4c8777d6e..ca3910215 100644
--- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/FileSizeQuotaExceededException.java
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/FileSizeQuotaExceededException.java
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2019 Bosch Software Innovations GmbH and others
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
@@ -7,9 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.repository.exception;
-
-import static org.eclipse.hawkbit.repository.SizeConversionHelper.byteValueToReadableString;
+package org.eclipse.hawkbit.artifact.exception;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@@ -26,28 +24,28 @@ public class FileSizeQuotaExceededException extends AbstractServerRtException {
private static final String MAX_ARTIFACT_SIZE_EXCEEDED = "Maximum artifact size (%s) exceeded.";
private static final SpServerError errorType = SpServerError.SP_FILE_SIZE_QUOTA_EXCEEDED;
+ private static final String KB = "KB";
+ private static final String MB = "MB";
+
private final long exceededQuotaValue;
- /**
- * Creates a new FileSizeQuotaExceededException with a quota value.
- *
- * @param exceededQuotaValue Value by how much the quota was exceeded
- */
public FileSizeQuotaExceededException(final long exceededQuotaValue) {
- super(createQuotaErrorMessage(exceededQuotaValue), errorType);
+ super(errorType, createQuotaErrorMessage(exceededQuotaValue));
this.exceededQuotaValue = exceededQuotaValue;
}
- /**
- * Get a readable string of size quota including unit
- *
- * @return file size quota with unit
- */
- public String getExceededQuotaValueString() {
- return byteValueToReadableString(exceededQuotaValue);
- }
-
private static String createQuotaErrorMessage(final long exceededQuotaValue) {
return String.format(MAX_ARTIFACT_SIZE_EXCEEDED, byteValueToReadableString(exceededQuotaValue));
}
+
+ static String byteValueToReadableString(final long byteValue) {
+ double outputValue = byteValue / 1024.0;
+ String unit = KB;
+ if (outputValue >= 1024) {
+ outputValue = outputValue / 1024.0;
+ unit = MB;
+ }
+ // We cut decimal places to avoid localization handling
+ return (long) outputValue + " " + unit;
+ }
}
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/HashNotMatchException.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/HashNotMatchException.java
similarity index 78%
rename from hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/HashNotMatchException.java
rename to hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/HashNotMatchException.java
index 21da96e50..c0eab3204 100644
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/exception/HashNotMatchException.java
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/HashNotMatchException.java
@@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.repository.artifact.exception;
+package org.eclipse.hawkbit.artifact.exception;
import java.io.Serial;
@@ -28,12 +28,6 @@ public class HashNotMatchException extends RuntimeException {
private final String hashFunction;
- /**
- * Constructs a HashNotMatchException with message.
- *
- * @param message the message of the exception
- * @param hashFunction the hash function which caused this exception
- */
public HashNotMatchException(final String message, final String hashFunction) {
super(message);
this.hashFunction = hashFunction;
diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/StorageQuotaExceededException.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/StorageQuotaExceededException.java
similarity index 57%
rename from hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/StorageQuotaExceededException.java
rename to hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/StorageQuotaExceededException.java
index bdc12826b..d06b907a1 100644
--- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/StorageQuotaExceededException.java
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/exception/StorageQuotaExceededException.java
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2019 Bosch Software Innovations GmbH and others
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
@@ -7,9 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.repository.exception;
-
-import static org.eclipse.hawkbit.repository.SizeConversionHelper.byteValueToReadableString;
+package org.eclipse.hawkbit.artifact.exception;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@@ -28,26 +26,12 @@ public class StorageQuotaExceededException extends AbstractServerRtException {
private final long exceededQuotaValue;
- /**
- * Creates a new StorageQuotaExceededException with a quota value.
- *
- * @param exceededQuotaValue Value by how much the quota was exceeded
- */
public StorageQuotaExceededException(final long exceededQuotaValue) {
- super(createQuotaErrorMessage(exceededQuotaValue), errorType);
+ super(errorType, createQuotaErrorMessage(exceededQuotaValue));
this.exceededQuotaValue = exceededQuotaValue;
}
- /**
- * Get a readable string of size quota including unit
- *
- * @return file size quota with unit
- */
- public String getExceededQuotaValueString() {
- return byteValueToReadableString(exceededQuotaValue);
- }
-
private static String createQuotaErrorMessage(final long exceededQuotaValue) {
- return String.format(MAX_ARTIFACT_SIZE_TOTAL_EXCEEDED, byteValueToReadableString(exceededQuotaValue));
+ return String.format(MAX_ARTIFACT_SIZE_TOTAL_EXCEEDED, FileSizeQuotaExceededException.byteValueToReadableString(exceededQuotaValue));
}
-}
+}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/model/ArtifactHashes.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/model/ArtifactHashes.java
new file mode 100644
index 000000000..d659c1e2b
--- /dev/null
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/model/ArtifactHashes.java
@@ -0,0 +1,15 @@
+/**
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.hawkbit.artifact.model;
+
+/**
+ * Representation of artifact hashes.
+ */
+public record ArtifactHashes(String sha1, String md5, String sha256) {}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/model/ArtifactStream.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/model/ArtifactStream.java
new file mode 100644
index 000000000..d0bc5bd5c
--- /dev/null
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/model/ArtifactStream.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.hawkbit.artifact.model;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+
+@EqualsAndHashCode(callSuper = false)
+@ToString
+public class ArtifactStream extends InputStream {
+
+ @EqualsAndHashCode.Exclude
+ @ToString.Exclude
+ private final InputStream inputStream;
+ @Getter
+ private final long size;
+ @Getter
+ private final String sha1Hash;
+
+ public ArtifactStream(InputStream inputStream, long size, String sha1Hash) {
+ this.inputStream = inputStream;
+ this.size = size;
+ this.sha1Hash = sha1Hash;
+ }
+
+ @Override
+ public int read() throws IOException {
+ return inputStream.read();
+ }
+
+ @Override
+ public int read(final byte[] b, final int off, final int len) throws IOException {
+ return inputStream.read(b, off, len);
+ }
+
+ @Override
+ public void close() throws IOException {
+ inputStream.close();
+ }
+}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/model/AbstractDbArtifact.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/model/StoredArtifactInfo.java
similarity index 55%
rename from hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/model/AbstractDbArtifact.java
rename to hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/model/StoredArtifactInfo.java
index 9229e163b..059246973 100644
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/model/AbstractDbArtifact.java
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/model/StoredArtifactInfo.java
@@ -7,28 +7,25 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.repository.artifact.model;
+package org.eclipse.hawkbit.artifact.model;
import java.util.Objects;
import lombok.Data;
/**
- * Database representation of artifact.
+ * Info for an imported artifact binary.
*/
@Data
-public abstract class AbstractDbArtifact implements DbArtifact {
+public class StoredArtifactInfo {
- private final String artifactId;
- private final long size;
private final String contentType;
+ private final long size;
+ private final ArtifactHashes hashes;
- private DbArtifactHash hashes;
-
- protected AbstractDbArtifact(final String artifactId, final DbArtifactHash hashes, final long size, final String contentType) {
- this.artifactId = Objects.requireNonNull(artifactId, "Artifact ID cannot be null");
+ public StoredArtifactInfo(final String contentType, final long size, final ArtifactHashes hashes) {
this.hashes = Objects.requireNonNull(hashes, "Hashes cannot be null");
- this.size = size;
this.contentType = contentType;
+ this.size = size;
}
}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/urlresolver/ArtifactUrl.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/urlresolver/ArtifactUrl.java
new file mode 100644
index 000000000..2f0f8c63a
--- /dev/null
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/urlresolver/ArtifactUrl.java
@@ -0,0 +1,15 @@
+/**
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.hawkbit.artifact.urlresolver;
+
+/**
+ * Container for a generated Artifact URL.
+ */
+public record ArtifactUrl(String protocol, String rel, String ref) {}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/urlresolver/ArtifactUrlResolver.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/urlresolver/ArtifactUrlResolver.java
new file mode 100644
index 000000000..42ee29d5f
--- /dev/null
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/urlresolver/ArtifactUrlResolver.java
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.hawkbit.artifact.urlresolver;
+
+import java.net.URI;
+import java.util.List;
+
+/**
+ * Interface declaration of the {@link ArtifactUrlResolver} which generates the URLs to specific artifacts.
+ */
+public interface ArtifactUrlResolver {
+
+ /**
+ * hawkBit API type.
+ */
+ enum ApiType {
+
+ /**
+ * Support for Device Management Federation API.
+ */
+ DMF,
+
+ /**
+ * Support for Direct Device Integration API.
+ */
+ DDI,
+
+ /**
+ * Support for Management API.
+ */
+ MGMT
+ }
+
+ /**
+ * Returns a generated download URL for a given artifact parameters for a specific protocol.
+ *
+ * @param downloadDescriptor data for URL generation
+ * @param api given protocol that URL needs to support
+ * @return a URL for the given artifact parameters in a given protocol
+ */
+ List getUrls(DownloadDescriptor downloadDescriptor, ApiType api);
+
+ /**
+ * Returns a generated download URL for a given artifact parameters for a specific protocol.
+ *
+ * @param downloadDescriptor data for URL generation
+ * @param api given protocol that URL needs to support
+ * @param requestUri of the request that allows the handler to align the generated URL to the original request.
+ * @return a URL for the given artifact parameters in a given protocol
+ */
+ List getUrls(DownloadDescriptor downloadDescriptor, ApiType api, URI requestUri);
+
+ /**
+ * Container for variables available to the {@link ArtifactUrlResolver}.
+ */
+ record DownloadDescriptor(String tenant, String controllerId, Long softwareModuleId, String filename, String sha1) {}
+}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/urlresolver/PropertyBasedArtifactUrlResolver.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/urlresolver/PropertyBasedArtifactUrlResolver.java
new file mode 100644
index 000000000..36d700855
--- /dev/null
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/urlresolver/PropertyBasedArtifactUrlResolver.java
@@ -0,0 +1,152 @@
+/**
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.hawkbit.artifact.urlresolver;
+
+import java.net.URI;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+
+import org.eclipse.hawkbit.artifact.urlresolver.PropertyBasedArtifactUrlResolverProperties.UrlProtocol;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * Implementation for ArtifactUrlHandler for creating urls to download resource based on patterns configured by
+ * {@link PropertyBasedArtifactUrlResolverProperties}.
+ *
+ * This mechanism can be used to generate links to arbitrary file hosting infrastructure. However, the hawkBit update server
+ * supports hosting files as well in the following {@link UrlProtocol#getRef()} patterns:
+ *
+ * Default:
+ * {protocol}://{hostname}:{port}{contextPath}/{tenant}/controller/v1/{controllerId}
+ * /softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}
+ *
+ * Default (MD5SUM files):
+ * {protocol}://{hostname}:{port}{contextPath}/{tenant}/controller/v1/{controllerId}/
+ * softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}.MD5SUM
+ */
+public class PropertyBasedArtifactUrlResolver implements ArtifactUrlResolver {
+
+ private static final String PROTOCOL_PLACEHOLDER = "protocol";
+ private static final String PROTOCOL_REQUEST_PLACEHOLDER = "protocolRequest";
+ private static final String HOSTNAME_PLACEHOLDER = "hostname";
+ private static final String HOSTNAME_REQUEST_PLACEHOLDER = "hostnameRequest";
+ private static final String HOSTNAME_WITH_DOMAIN_REQUEST_PLACEHOLDER = "domainRequest";
+ private static final String IP_PLACEHOLDER = "ip";
+ private static final String PORT_PLACEHOLDER = "port";
+ private static final String PORT_REQUEST_PLACEHOLDER = "portRequest";
+ private static final String TENANT_PLACEHOLDER = "tenant";
+ private static final String CONTEXT_PATH = "contextPath";
+ private static final String CONTROLLER_ID_PLACEHOLDER = "controllerId";
+ private static final String SOFTWARE_MODULE_ID_PLACEHOLDER = "softwareModuleId";
+ private static final String ARTIFACT_FILENAME_PLACEHOLDER = "artifactFileName";
+ private static final String ARTIFACT_SHA1_PLACEHOLDER = "artifactSHA1";
+ // by default, we download via the controller / DDI API download endpoint
+ static final String DEFAULT_URL_PROTOCOL_REF = "{" + PROTOCOL_REQUEST_PLACEHOLDER + "}://{" + HOSTNAME_REQUEST_PLACEHOLDER + "}:{" + PORT_REQUEST_PLACEHOLDER + "}{" + CONTEXT_PATH + "}/{" + TENANT_PLACEHOLDER + "}/controller/v1/{" + CONTROLLER_ID_PLACEHOLDER + "}/softwaremodules/{" + SOFTWARE_MODULE_ID_PLACEHOLDER + "}/artifacts/{" + ARTIFACT_FILENAME_PLACEHOLDER + "}";
+
+ private final PropertyBasedArtifactUrlResolverProperties urlHandlerProperties;
+ private final String contextPath;
+
+ @SuppressWarnings("java:S3358") // better readable this way
+ public PropertyBasedArtifactUrlResolver(final PropertyBasedArtifactUrlResolverProperties urlHandlerProperties, final String contextPath) {
+ this.urlHandlerProperties = urlHandlerProperties;
+ this.contextPath = ObjectUtils.isEmpty(contextPath) || "/".equals(contextPath)
+ ? ""
+ : (contextPath.charAt(0) == '/' ? contextPath : '/' + contextPath); // normalize context path
+ }
+
+ @Override
+ public List getUrls(final DownloadDescriptor downloadDescriptor, final ApiType api) {
+ return getUrls(downloadDescriptor, api, null);
+ }
+
+ @Override
+ public List getUrls(final DownloadDescriptor downloadDescriptor, final ApiType api, final URI requestUri) {
+ return urlHandlerProperties.getProtocols().values().stream()
+ .filter(urlProtocol -> urlProtocol.isEnabled() && urlProtocol.getSupports().contains(api))
+ .map(urlProtocol -> new ArtifactUrl(
+ urlProtocol.getProtocol().toUpperCase(), urlProtocol.getRel(),
+ generateUrl(urlProtocol, downloadDescriptor, requestUri)))
+ .toList();
+ }
+
+ private String generateUrl(final UrlProtocol protocol, final DownloadDescriptor placeholder, final URI requestUri) {
+ final Set> entrySet = getReplaceMap(protocol, placeholder, requestUri).entrySet();
+ String urlPattern = protocol.getRef();
+ for (final Entry entry : entrySet) {
+ if (List.of(PORT_PLACEHOLDER, PORT_REQUEST_PLACEHOLDER).contains(entry.getKey())) {
+ urlPattern = urlPattern.replace(":{" + entry.getKey() + "}",
+ ObjectUtils.isEmpty(entry.getValue()) ? "" : (":" + entry.getValue()));
+ } else {
+ if (entry.getValue() != null) {
+ urlPattern = urlPattern.replace("{" + entry.getKey() + "}", entry.getValue());
+ }
+ }
+ }
+ return urlPattern;
+ }
+
+ private Map getReplaceMap(final UrlProtocol protocol, final DownloadDescriptor placeholder, final URI requestUri) {
+ final Map replaceMap = new HashMap<>();
+
+ replaceMap.put(PROTOCOL_PLACEHOLDER, protocol.getProtocol());
+ replaceMap.put(PROTOCOL_REQUEST_PLACEHOLDER, Optional.ofNullable(requestUri).map(URI::getScheme).orElseGet(protocol::getProtocol));
+
+ replaceMap.put(HOSTNAME_PLACEHOLDER, protocol.getHostname());
+ replaceMap.put(IP_PLACEHOLDER, protocol.getIp());
+ replaceMap.put(HOSTNAME_REQUEST_PLACEHOLDER, Optional.ofNullable(requestUri).map(URI::getHost).orElseGet(protocol::getHostname));
+ replaceMap.put(HOSTNAME_WITH_DOMAIN_REQUEST_PLACEHOLDER, computeHostWithRequestDomain(protocol, requestUri));
+
+ replaceMap.put(PORT_PLACEHOLDER, getPort(protocol));
+ replaceMap.put(
+ PORT_REQUEST_PLACEHOLDER,
+ Optional.ofNullable(requestUri)
+ .map(URI::getPort)
+ .map(port -> port > 0 ? String.valueOf(port) : "")
+ .orElseGet(() -> getPort(protocol)));
+
+ replaceMap.put(CONTEXT_PATH, contextPath);
+
+ replaceMap.put(TENANT_PLACEHOLDER, placeholder.tenant());
+ replaceMap.put(CONTROLLER_ID_PLACEHOLDER, placeholder.controllerId());
+ replaceMap.put(SOFTWARE_MODULE_ID_PLACEHOLDER, String.valueOf(placeholder.softwareModuleId()));
+ replaceMap.put(ARTIFACT_FILENAME_PLACEHOLDER, URLEncoder.encode(placeholder.filename(), StandardCharsets.UTF_8));
+ replaceMap.put(ARTIFACT_SHA1_PLACEHOLDER, placeholder.sha1());
+ return replaceMap;
+ }
+
+ private static String getPort(final UrlProtocol protocol) {
+ return ObjectUtils.isEmpty(protocol.getPort()) ? null : String.valueOf(protocol.getPort());
+ }
+
+ private static String computeHostWithRequestDomain(final UrlProtocol protocol, final URI requestUri) {
+ if (requestUri == null) {
+ return protocol.getHostname();
+ }
+ if (!protocol.getHostname().contains(".")) {
+ return protocol.getHostname();
+ }
+
+ final List domainElements = Arrays.asList(StringUtils.delimitedListToStringArray(requestUri.getHost(), "."));
+ if (domainElements.isEmpty()) {
+ return protocol.getHostname();
+ }
+
+ final String domain = StringUtils.collectionToDelimitedString(domainElements.subList(1, domainElements.size()), ".");
+ return StringUtils.delimitedListToStringArray(protocol.getHostname(), ".")[0].trim() + "." + domain;
+ }
+}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/ArtifactUrlHandlerProperties.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/urlresolver/PropertyBasedArtifactUrlResolverProperties.java
similarity index 76%
rename from hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/ArtifactUrlHandlerProperties.java
rename to hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/urlresolver/PropertyBasedArtifactUrlResolverProperties.java
index 1be88147c..65d5cde78 100644
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/ArtifactUrlHandlerProperties.java
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/urlresolver/PropertyBasedArtifactUrlResolverProperties.java
@@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.repository.artifact.urlhandler;
+package org.eclipse.hawkbit.artifact.urlresolver;
import java.util.Arrays;
import java.util.Collections;
@@ -19,14 +19,13 @@ import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
- * Artifact handler properties class for holding all supported protocols with
- * host, ip, port and download pattern.
+ * Artifact handler properties class for holding all supported protocols with host, ip, port and download pattern.
*
- * @see PropertyBasedArtifactUrlHandler
+ * @see PropertyBasedArtifactUrlResolver
*/
@Data
@ConfigurationProperties("hawkbit.artifact.url")
-public class ArtifactUrlHandlerProperties {
+public class PropertyBasedArtifactUrlResolverProperties {
/**
* Rel as key and complete protocol as value.
@@ -53,9 +52,9 @@ public class ArtifactUrlHandlerProperties {
/**
* Hypermedia ref pattern for this protocol. Supported placeholders are the properties
- * supported by {@link PropertyBasedArtifactUrlHandler}.
+ * supported by {@link PropertyBasedArtifactUrlResolver}.
*/
- private String ref = PropertyBasedArtifactUrlHandler.DEFAULT_URL_PROTOCOL_REF;
+ private String ref = PropertyBasedArtifactUrlResolver.DEFAULT_URL_PROTOCOL_REF;
/**
* Protocol name placeholder that can be used in ref pattern.
@@ -70,8 +69,6 @@ public class ArtifactUrlHandlerProperties {
/**
* IP address placeholder that can be used in ref pattern.
*/
- // Exception squid:S1313 - default only, can be configured
- @SuppressWarnings("squid:S1313")
private String ip = "127.0.0.1";
/**
@@ -82,9 +79,9 @@ public class ArtifactUrlHandlerProperties {
/**
* Support for the following hawkBit API.
*/
- private List supports = Arrays.asList(ApiType.DDI, ApiType.DMF, ApiType.MGMT);
+ private List supports = Arrays.asList(ArtifactUrlResolver.ApiType.DDI, ArtifactUrlResolver.ApiType.DMF, ArtifactUrlResolver.ApiType.MGMT);
- public void setSupports(final List supports) {
+ public void setSupports(final List supports) {
this.supports = Collections.unmodifiableList(supports);
}
}
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/model/DbArtifact.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/model/DbArtifact.java
deleted file mode 100644
index 166162d14..000000000
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/model/DbArtifact.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * Copyright (c) 2025 Contributors to the Eclipse Foundation
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.eclipse.hawkbit.repository.artifact.model;
-
-import java.io.InputStream;
-
-/**
- * Interface definition for artifact binary.
- */
-public interface DbArtifact {
-
- /**
- * @return ID of the artifact
- */
- String getArtifactId();
-
- /**
- * @return hashes of the artifact
- */
- DbArtifactHash getHashes();
-
- /**
- * @return size of the artifact in bytes
- */
- long getSize();
-
- /**
- * @return content-type if known by the repository or null
- */
- String getContentType();
-
- /**
- * Creates an {@link InputStream} on this artifact. Caller has to take care of
- * closing the stream. Repeatable calls open a new {@link InputStream}.
- *
- * @return {@link InputStream} to read from artifact.
- */
- InputStream getFileInputStream();
-}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/model/DbArtifactHash.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/model/DbArtifactHash.java
deleted file mode 100644
index 17a5e8f67..000000000
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/model/DbArtifactHash.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * Copyright (c) 2025 Contributors to the Eclipse Foundation
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.eclipse.hawkbit.repository.artifact.model;
-
-import lombok.Data;
-
-/**
- * Database representation of artifact hash.
- */
-@Data
-public class DbArtifactHash {
-
- private final String sha1;
- private final String md5;
- private final String sha256;
-
- /**
- * Constructor.
- *
- * @param sha1 the sha1 hash
- * @param md5 the md5 hash
- * @param sha256 the sha256 hash
- */
- public DbArtifactHash(final String sha1, final String md5, final String sha256) {
- this.sha1 = sha1;
- this.md5 = md5;
- this.sha256 = sha256;
- }
-}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/ApiType.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/ApiType.java
deleted file mode 100644
index 6ff9589db..000000000
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/ApiType.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * Copyright (c) 2025 Contributors to the Eclipse Foundation
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.eclipse.hawkbit.repository.artifact.urlhandler;
-
-/**
- * hawkBit API type.
- */
-public enum ApiType {
-
- /**
- * Support for Device Management Federation API.
- */
- DMF,
-
- /**
- * Support for Direct Device Integration API.
- */
- DDI,
-
- /**
- * Support for Management API.
- */
- MGMT
-}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/ArtifactUrl.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/ArtifactUrl.java
deleted file mode 100644
index c7332dac0..000000000
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/ArtifactUrl.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * Copyright (c) 2025 Contributors to the Eclipse Foundation
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.eclipse.hawkbit.repository.artifact.urlhandler;
-
-import lombok.Data;
-
-/**
- * Container for a generated Artifact URL.
- */
-@Data
-public class ArtifactUrl {
-
- private final String protocol;
- private final String rel;
- private final String ref;
-
- /**
- * Constructor.
- *
- * @param protocol string, e.g. ftp, http, https
- * @param rel hypermedia value
- * @param ref hypermedia value
- */
- public ArtifactUrl(final String protocol, final String rel, final String ref) {
- this.protocol = protocol;
- this.rel = rel;
- this.ref = ref;
- }
-}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/ArtifactUrlHandler.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/ArtifactUrlHandler.java
deleted file mode 100644
index 940d32acc..000000000
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/ArtifactUrlHandler.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * Copyright (c) 2025 Contributors to the Eclipse Foundation
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.eclipse.hawkbit.repository.artifact.urlhandler;
-
-import java.net.URI;
-import java.util.List;
-
-/**
- * Interface declaration of the {@link ArtifactUrlHandler} which generates the URLs to specific artifacts.
- */
-public interface ArtifactUrlHandler {
-
- /**
- * Returns a generated download URL for a given artifact parameters for a specific protocol.
- *
- * @param placeholder data for URL generation
- * @param api given protocol that URL needs to support
- * @return a URL for the given artifact parameters in a given protocol
- */
- List getUrls(URLPlaceholder placeholder, ApiType api);
-
- /**
- * Returns a generated download URL for a given artifact parameters for a
- * specific protocol.
- *
- * @param placeholder data for URL generation
- * @param api given protocol that URL needs to support
- * @param requestUri of the request that allows the handler to align the generated URL to the original request.
- * @return a URL for the given artifact parameters in a given protocol
- */
- List getUrls(URLPlaceholder placeholder, ApiType api, URI requestUri);
-}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/Base62Util.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/Base62Util.java
deleted file mode 100644
index b86416a1b..000000000
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/Base62Util.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/**
- * Copyright (c) 2025 Contributors to the Eclipse Foundation
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.eclipse.hawkbit.repository.artifact.urlhandler;
-
-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
-
-/**
- * Utility class for Base10 to Base62 conversion and vice versa. Base62 has the benefit of being shorter in ASCII representation than Base10.
- */
-@NoArgsConstructor(access = AccessLevel.PRIVATE)
-final class Base62Util {
-
- private static final String BASE62_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
- private static final int BASE62_BASE = BASE62_ALPHABET.length();
-
- /**
- * @param l number
- * @return converted number into Base62 ASCII string
- */
- static String fromBase10(final long l) {
- if (l == 0) {
- return "0";
- }
-
- long temp = l;
- final StringBuilder sb = new StringBuilder();
-
- while (temp > 0) {
- temp = fromBase10(temp, sb);
- }
- return sb.reverse().toString();
- }
-
- /**
- * @param base62 number
- * @return converted number into Base10
- */
- static Long toBase10(final String base62) {
- return toBase10(new StringBuilder(base62).reverse().toString().toCharArray());
- }
-
- static Long fromBase10(final long l, final StringBuilder sb) {
- final int rem = (int) (l % BASE62_BASE);
- sb.append(BASE62_ALPHABET.charAt(rem));
- return l / BASE62_BASE;
- }
-
- private static Long toBase10(final char[] chars) {
- long base10 = 0L;
- for (int i = chars.length - 1; i >= 0; i--) {
- base10 += toBase10(BASE62_ALPHABET.indexOf(chars[i]), i);
- }
- return base10;
- }
-
- private static int toBase10(final int n, final int pow) {
- return n * (int) Math.pow(BASE62_BASE, pow);
- }
-}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/PropertyBasedArtifactUrlHandler.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/PropertyBasedArtifactUrlHandler.java
deleted file mode 100644
index eb67562bd..000000000
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/PropertyBasedArtifactUrlHandler.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/**
- * Copyright (c) 2025 Contributors to the Eclipse Foundation
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.eclipse.hawkbit.repository.artifact.urlhandler;
-
-import java.net.URI;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Optional;
-import java.util.Set;
-
-import org.eclipse.hawkbit.repository.artifact.urlhandler.ArtifactUrlHandlerProperties.UrlProtocol;
-import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
-
-/**
- * Implementation for ArtifactUrlHandler for creating urls to download resource based on patterns configured by
- * {@link ArtifactUrlHandlerProperties}.
- *
- * This mechanism can be used to generate links to arbitrary file hosting infrastructure. However, the hawkBit update server
- * supports hosting files as well in the following {@link UrlProtocol#getRef()} patterns:
- *
- * Default:
- * {protocol}://{hostname}:{port}{contextPath}/{tenant}/controller/v1/{controllerId}
- * /softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}
- *
- * Default (MD5SUM files):
- * {protocol}://{hostname}:{port}{contextPath}/{tenant}/controller/v1/{controllerId}/
- * softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}.MD5SUM
- */
-public class PropertyBasedArtifactUrlHandler implements ArtifactUrlHandler {
-
- private static final String PROTOCOL_PLACEHOLDER = "protocol";
- private static final String HOSTNAME_PLACEHOLDER = "hostname";
- private static final String IP_PLACEHOLDER = "ip";
- private static final String PORT_PLACEHOLDER = "port";
- private static final String CONTEXT_PATH = "contextPath";
- private static final String CONTROLLER_ID_PLACEHOLDER = "controllerId";
- private static final String TARGET_ID_BASE10_PLACEHOLDER = "targetId";
- private static final String TARGET_ID_BASE62_PLACEHOLDER = "targetIdBase62";
- private static final String HOSTNAME_REQUEST_PLACEHOLDER = "hostnameRequest";
- private static final String PORT_REQUEST_PLACEHOLDER = "portRequest";
- private static final String PROTOCOL_REQUEST_PLACEHOLDER = "protocolRequest";
- private static final String HOSTNAME_WITH_DOMAIN_REQUEST_PLACEHOLDER = "domainRequest";
- private static final String ARTIFACT_FILENAME_PLACEHOLDER = "artifactFileName";
- private static final String ARTIFACT_SHA1_PLACEHOLDER = "artifactSHA1";
- private static final String ARTIFACT_ID_BASE10_PLACEHOLDER = "artifactId";
- private static final String ARTIFACT_ID_BASE62_PLACEHOLDER = "artifactIdBase62";
- private static final String TENANT_PLACEHOLDER = "tenant";
- private static final String TENANT_ID_BASE10_PLACEHOLDER = "tenantId";
- private static final String TENANT_ID_BASE62_PLACEHOLDER = "tenantIdBase62";
- private static final String SOFTWARE_MODULE_ID_BASE10_PLACEHOLDER = "softwareModuleId";
- private static final String SOFTWARE_MODULE_ID_BASE62_PLACEHOLDER = "softwareModuleIdBase62";
- static final String DEFAULT_URL_PROTOCOL_REF = "{" + PROTOCOL_PLACEHOLDER + "}://{" + HOSTNAME_PLACEHOLDER + "}:{" + PORT_PLACEHOLDER + "}{" + CONTEXT_PATH + "}/{" + TENANT_PLACEHOLDER + "}/controller/v1/{" + CONTROLLER_ID_PLACEHOLDER + "}/softwaremodules/{" + SOFTWARE_MODULE_ID_BASE10_PLACEHOLDER + "}/artifacts/{" + ARTIFACT_FILENAME_PLACEHOLDER + "}";
-
- private final ArtifactUrlHandlerProperties urlHandlerProperties;
- private final String contextPath;
-
- /**
- * @param urlHandlerProperties for URL generation configuration
- */
- public PropertyBasedArtifactUrlHandler(final ArtifactUrlHandlerProperties urlHandlerProperties, final String contextPath) {
- this.urlHandlerProperties = urlHandlerProperties;
- this.contextPath = contextPath == null || "/".equals(contextPath) ? "" : contextPath; // normalize
- }
-
- @Override
- public List getUrls(final URLPlaceholder placeholder, final ApiType api) {
- return getUrls(placeholder, api, null);
- }
-
- @Override
- public List getUrls(final URLPlaceholder placeholder, final ApiType api, final URI requestUri) {
- return urlHandlerProperties.getProtocols().values().stream()
- .filter(urlProtocol -> urlProtocol.getSupports().contains(api) && urlProtocol.isEnabled())
- .map(urlProtocol -> new ArtifactUrl(urlProtocol.getProtocol().toUpperCase(), urlProtocol.getRel(),
- generateUrl(urlProtocol, placeholder, requestUri)))
- .toList();
-
- }
-
- private static String getRequestPort(final UrlProtocol protocol, final URI requestUri) {
- if (requestUri == null) {
- return getPort(protocol);
- }
- // if port undefined then default protocol port is used
- return requestUri.getPort() > 0 ? String.valueOf(requestUri.getPort()) : "";
- }
-
- private static String getRequestHost(final UrlProtocol protocol, final URI requestUri) {
- if (requestUri == null) {
- return protocol.getHostname();
- }
-
- return Optional.ofNullable(requestUri.getHost()).orElse(protocol.getHostname());
- }
-
- private static String getRequestProtocol(final UrlProtocol protocol, final URI requestUri) {
- if (requestUri == null) {
- return protocol.getProtocol();
- }
-
- return Optional.ofNullable(requestUri.getScheme()).orElse(protocol.getProtocol());
- }
-
- private static String getPort(final UrlProtocol protocol) {
- return ObjectUtils.isEmpty(protocol.getPort()) ? null : String.valueOf(protocol.getPort());
- }
-
- private static String computeHostWithRequestDomain(final UrlProtocol protocol, final URI requestUri) {
-
- if (requestUri == null) {
- return protocol.getHostname();
- }
-
- if (!protocol.getHostname().contains(".")) {
- return protocol.getHostname();
- }
-
- final String host = StringUtils.delimitedListToStringArray(protocol.getHostname(), ".")[0].trim();
-
- final List domainElements = Arrays
- .asList(StringUtils.delimitedListToStringArray(requestUri.getHost(), "."));
- final String domain = StringUtils.collectionToDelimitedString(domainElements.subList(1, domainElements.size()),
- ".");
-
- if (ObjectUtils.isEmpty(domain)) {
- return protocol.getHostname();
- }
-
- return host + "." + domain;
- }
-
- private String generateUrl(final UrlProtocol protocol, final URLPlaceholder placeholder,
- final URI requestUri) {
- final Set> entrySet = getReplaceMap(protocol, placeholder, requestUri).entrySet();
-
- String urlPattern = protocol.getRef();
-
- for (final Entry entry : entrySet) {
- if (List.of(PORT_PLACEHOLDER, PORT_REQUEST_PLACEHOLDER).contains(entry.getKey())) {
- urlPattern = urlPattern.replace(":{" + entry.getKey() + "}",
- ObjectUtils.isEmpty(entry.getValue()) ? "" : (":" + entry.getValue()));
- } else {
- if (entry.getValue() != null) {
- urlPattern = urlPattern.replace("{" + entry.getKey() + "}", entry.getValue());
- }
- }
- }
-
- return urlPattern;
- }
-
- private Map getReplaceMap(final UrlProtocol protocol, final URLPlaceholder placeholder,
- final URI requestUri) {
- final Map replaceMap = new HashMap<>();
- replaceMap.put(IP_PLACEHOLDER, protocol.getIp());
-
- replaceMap.put(HOSTNAME_PLACEHOLDER, protocol.getHostname());
-
- replaceMap.put(HOSTNAME_REQUEST_PLACEHOLDER, getRequestHost(protocol, requestUri));
- replaceMap.put(PORT_REQUEST_PLACEHOLDER, getRequestPort(protocol, requestUri));
- replaceMap.put(HOSTNAME_WITH_DOMAIN_REQUEST_PLACEHOLDER, computeHostWithRequestDomain(protocol, requestUri));
- replaceMap.put(PROTOCOL_REQUEST_PLACEHOLDER, getRequestProtocol(protocol, requestUri));
-
- replaceMap.put(CONTEXT_PATH, contextPath);
-
- replaceMap.put(ARTIFACT_FILENAME_PLACEHOLDER,
- URLEncoder.encode(placeholder.getSoftwareData().getFilename(), StandardCharsets.UTF_8));
-
- replaceMap.put(ARTIFACT_SHA1_PLACEHOLDER, placeholder.getSoftwareData().getSha1Hash());
- replaceMap.put(PROTOCOL_PLACEHOLDER, protocol.getProtocol());
- replaceMap.put(PORT_PLACEHOLDER, getPort(protocol));
- replaceMap.put(TENANT_PLACEHOLDER, placeholder.getTenant());
- replaceMap.put(TENANT_ID_BASE10_PLACEHOLDER, String.valueOf(placeholder.getTenantId()));
- replaceMap.put(TENANT_ID_BASE62_PLACEHOLDER, Base62Util.fromBase10(placeholder.getTenantId()));
- replaceMap.put(CONTROLLER_ID_PLACEHOLDER, placeholder.getControllerId());
- replaceMap.put(TARGET_ID_BASE10_PLACEHOLDER, String.valueOf(placeholder.getTargetId()));
- if (placeholder.getTargetId() != null) {
- replaceMap.put(TARGET_ID_BASE62_PLACEHOLDER, Base62Util.fromBase10(placeholder.getTargetId()));
- }
- replaceMap.put(ARTIFACT_ID_BASE62_PLACEHOLDER,
- Base62Util.fromBase10(placeholder.getSoftwareData().getArtifactId()));
- replaceMap.put(ARTIFACT_ID_BASE10_PLACEHOLDER, String.valueOf(placeholder.getSoftwareData().getArtifactId()));
- replaceMap.put(SOFTWARE_MODULE_ID_BASE10_PLACEHOLDER,
- String.valueOf(placeholder.getSoftwareData().getSoftwareModuleId()));
- replaceMap.put(SOFTWARE_MODULE_ID_BASE62_PLACEHOLDER,
- Base62Util.fromBase10(placeholder.getSoftwareData().getSoftwareModuleId()));
- return replaceMap;
- }
-
-}
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/URLPlaceholder.java b/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/URLPlaceholder.java
deleted file mode 100644
index 940540bc1..000000000
--- a/hawkbit-artifact/hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/repository/artifact/urlhandler/URLPlaceholder.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * Copyright (c) 2025 Contributors to the Eclipse Foundation
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.eclipse.hawkbit.repository.artifact.urlhandler;
-
-import lombok.Data;
-
-/**
- * Container for variables available to the {@link ArtifactUrlHandler}.
- */
-@Data
-public class URLPlaceholder {
-
- private final String tenant;
- private final Long tenantId;
- private final String controllerId;
- private final Long targetId;
- private final SoftwareData softwareData;
-
- /**
- * Constructor.
- *
- * @param tenant of the client
- * @param tenantId of teh tenant
- * @param controllerId of the target
- * @param targetId of the target
- * @param softwareData information about the artifact and software module that can be accessed by the URL.
- */
- public URLPlaceholder(final String tenant, final Long tenantId, final String controllerId, final Long targetId,
- final SoftwareData softwareData) {
- this.tenant = tenant;
- this.tenantId = tenantId;
- this.controllerId = controllerId;
- this.targetId = targetId;
- this.softwareData = softwareData;
- }
-
- /**
- * Information about the artifact and software module that can be accessed
- * by the URL.
- */
- @Data
- public static class SoftwareData {
-
- private Long softwareModuleId;
- private String filename;
- private Long artifactId;
- private String sha1Hash;
-
- /**
- * Constructor.
- *
- * @param softwareModuleId of the module the artifact belongs to
- * @param filename of the artifact
- * @param artifactId of the artifact
- * @param sha1Hash of the artifact
- */
- public SoftwareData(final Long softwareModuleId, final String filename, final Long artifactId, final String sha1Hash) {
- this.softwareModuleId = softwareModuleId;
- this.filename = filename;
- this.artifactId = artifactId;
- this.sha1Hash = sha1Hash;
- }
- }
-}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/repository/artifact/encryption/ArtifactEncryptionServiceTest.java b/hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/artifact/encryption/ArtifactEncryptionServiceTest.java
similarity index 90%
rename from hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/repository/artifact/encryption/ArtifactEncryptionServiceTest.java
rename to hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/artifact/encryption/ArtifactEncryptionServiceTest.java
index 081ee5078..fc9ce4f7a 100644
--- a/hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/repository/artifact/encryption/ArtifactEncryptionServiceTest.java
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/artifact/encryption/ArtifactEncryptionServiceTest.java
@@ -7,12 +7,12 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.repository.artifact.encryption;
+package org.eclipse.hawkbit.artifact.encryption;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-import org.eclipse.hawkbit.repository.artifact.exception.ArtifactEncryptionUnsupportedException;
+import org.eclipse.hawkbit.artifact.exception.ArtifactEncryptionUnsupportedException;
import org.junit.jupiter.api.Test;
/**
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/repository/artifact/urlhandler/PropertyBasedArtifactUrlHandlerTest.java b/hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/artifact/urlresolver/PropertyBasedArtifactUrlResolverTest.java
similarity index 50%
rename from hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/repository/artifact/urlhandler/PropertyBasedArtifactUrlHandlerTest.java
rename to hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/artifact/urlresolver/PropertyBasedArtifactUrlResolverTest.java
index de924e19d..03473f5a8 100644
--- a/hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/repository/artifact/urlhandler/PropertyBasedArtifactUrlHandlerTest.java
+++ b/hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/artifact/urlresolver/PropertyBasedArtifactUrlResolverTest.java
@@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.repository.artifact.urlhandler;
+package org.eclipse.hawkbit.artifact.urlresolver;
import static org.assertj.core.api.Assertions.assertThat;
@@ -15,8 +15,8 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
-import org.eclipse.hawkbit.repository.artifact.urlhandler.ArtifactUrlHandlerProperties.UrlProtocol;
-import org.eclipse.hawkbit.repository.artifact.urlhandler.URLPlaceholder.SoftwareData;
+import org.eclipse.hawkbit.artifact.urlresolver.ArtifactUrlResolver.DownloadDescriptor;
+import org.eclipse.hawkbit.artifact.urlresolver.PropertyBasedArtifactUrlResolverProperties.UrlProtocol;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -29,55 +29,44 @@ import org.mockito.junit.jupiter.MockitoExtension;
* Story: Test to generate the artifact download URL
*/
@ExtendWith(MockitoExtension.class)
-class PropertyBasedArtifactUrlHandlerTest {
+class PropertyBasedArtifactUrlResolverTest {
- private static final String TEST_PROTO = "coap";
+ private static final String TEST_PROTO = "https";
private static final String TEST_REL = "download-udp";
- private static final long TENANT_ID = 456789L;
- private static final String CONTROLLER_ID = "Test";
- private static final String FILENAME_DECODE = "test123!§$%&";
- private static final String FILENAME_ENCODE = "test123%21%C2%A7%24%25%26";
- private static final long SOFTWARE_MODULE_ID = 87654L;
- private static final long TARGET_ID = 3474366L;
- private static final String TARGET_ID_BASE62 = "EZqA";
- private static final String SHA1HASH = "test12345";
- private static final long ARTIFACT_ID = 1345678L;
- private static final String ARTIFACT_ID_BASE62 = "5e4U";
private static final String TENANT = "TEST_TENANT";
+ private static final String CONTROLLER_ID = "Test";
+ private static final long SOFTWARE_MODULE_ID = 87654L;
+ private static final String FILENAME = "test123!§$%&";
+ private static final String FILENAME_ENCODED = "test123%21%C2%A7%24%25%26";
+ private static final String SHA1 = "0123456789012345678901234567890123456789";
private static final String HTTP_LOCALHOST = "http://localhost:8080/";
- private static final URLPlaceholder placeHolder = new URLPlaceholder(
- TENANT, TENANT_ID, CONTROLLER_ID, TARGET_ID,
- new SoftwareData(SOFTWARE_MODULE_ID, FILENAME_DECODE, ARTIFACT_ID, SHA1HASH));
- private ArtifactUrlHandler urlHandlerUnderTest;
- private ArtifactUrlHandlerProperties properties;
+ private static final DownloadDescriptor DOWNLOAD_DESCRIPTOR = new DownloadDescriptor(TENANT, CONTROLLER_ID, SOFTWARE_MODULE_ID, FILENAME,
+ SHA1);
+
+ private ArtifactUrlResolver urlHandlerUnderTest;
+ private PropertyBasedArtifactUrlResolverProperties properties;
@BeforeEach
void setup() {
- properties = new ArtifactUrlHandlerProperties();
- urlHandlerUnderTest = new PropertyBasedArtifactUrlHandler(properties, "");
+ properties = new PropertyBasedArtifactUrlResolverProperties();
+ urlHandlerUnderTest = new PropertyBasedArtifactUrlResolver(properties, "");
}
- /**
- * Tests the generation of http download url.
- */
@Test
void urlGenerationWithDefaultConfiguration() {
properties.getProtocols().put("download-http", new UrlProtocol());
- final List ddiUrls = urlHandlerUnderTest.getUrls(placeHolder, ApiType.DDI);
+ final List ddiUrls = urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DDI);
assertThat(ddiUrls).containsExactly(
- new ArtifactUrl(
- "http".toUpperCase(), "download-http",
- HTTP_LOCALHOST + TENANT + "/controller/v1/" +
- CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODE))
- .isEqualTo(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DMF));
+ new ArtifactUrl(
+ "http".toUpperCase(), "download-http",
+ HTTP_LOCALHOST + TENANT + "/controller/v1/" +
+ CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODED))
+ .isEqualTo(urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DMF));
}
- /**
- * Tests the generation of custom download url with a CoAP example that supports DMF only.
- */
@Test
void urlGenerationWithCustomConfiguration() {
final UrlProtocol proto = new UrlProtocol();
@@ -85,41 +74,17 @@ class PropertyBasedArtifactUrlHandlerTest {
proto.setPort(5683);
proto.setProtocol(TEST_PROTO);
proto.setRel(TEST_REL);
- proto.setSupports(List.of(ApiType.DMF));
- proto.setRef("{protocol}://{ip}:{port}/fw/{tenant}/{controllerId}/sha1/{artifactSHA1}");
+ proto.setSupports(List.of(ArtifactUrlResolver.ApiType.DMF));
+ proto.setRef("{protocol}://{ip}:{port}/fw/{tenant}/{controllerId}/sha1/{artifactFileName}");
properties.getProtocols().put(TEST_PROTO, proto);
- assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DDI)).isEmpty();
- assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DMF)).containsExactly(
+ assertThat(urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DDI)).isEmpty();
+ assertThat(urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DMF)).containsExactly(
new ArtifactUrl(
TEST_PROTO.toUpperCase(), TEST_REL,
- "coap://127.0.0.1:5683/fw/" + TENANT + "/" + CONTROLLER_ID + "/sha1/" + SHA1HASH));
+ TEST_PROTO + "://127.0.0.1:5683/fw/" + TENANT + "/" + CONTROLLER_ID + "/sha1/" + FILENAME_ENCODED));
}
- /**
- * Tests the generation of custom download url using Base62 references with a CoAP example that supports DMF only.
- */
- @Test
- void urlGenerationWithCustomShortConfiguration() {
- final UrlProtocol proto = new UrlProtocol();
- proto.setIp("127.0.0.1");
- proto.setPort(5683);
- proto.setProtocol(TEST_PROTO);
- proto.setRel(TEST_REL);
- proto.setSupports(List.of(ApiType.DMF));
- proto.setRef("{protocol}://{ip}:{port}/fws/{tenant}/{targetIdBase62}/{artifactIdBase62}");
- properties.getProtocols().put("ftp", proto);
-
- assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DDI)).isEmpty();
- assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DMF)).containsExactly(
- new ArtifactUrl(
- TEST_PROTO.toUpperCase(), TEST_REL,
- TEST_PROTO + "://127.0.0.1:5683/fws/" + TENANT + "/" + TARGET_ID_BASE62 + "/" + ARTIFACT_ID_BASE62));
- }
-
- /**
- * Verifies that the full qualified host of the statically defined hostname is replaced with the host of the request.
- */
@Test
void urlGenerationWithHostFromRequest() throws URISyntaxException {
final String testHost = "ddi.host.com";
@@ -129,14 +94,15 @@ class PropertyBasedArtifactUrlHandlerTest {
proto.setPort(5683);
proto.setProtocol(TEST_PROTO);
proto.setRel(TEST_REL);
- proto.setSupports(List.of(ApiType.DDI));
- proto.setRef("{protocol}://{hostnameRequest}:{port}/fws/{tenant}/{targetIdBase62}/{artifactIdBase62}");
+ proto.setSupports(List.of(ArtifactUrlResolver.ApiType.DDI));
+ proto.setRef("{protocol}://{hostnameRequest}:{port}/fws/{tenant}/{controllerId}/{artifactFileName}");
properties.getProtocols().put("ftp", proto);
- assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DDI, new URI("https://" + testHost))).containsExactly(
+ assertThat(urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DDI,
+ new URI("https://" + testHost))).containsExactly(
new ArtifactUrl(
TEST_PROTO.toUpperCase(), TEST_REL,
- TEST_PROTO + "://" + testHost + ":5683/fws/" + TENANT + "/" + TARGET_ID_BASE62 + "/" + ARTIFACT_ID_BASE62));
+ TEST_PROTO + "://" + testHost + ":5683/fws/" + TENANT + "/" + CONTROLLER_ID + "/" + FILENAME_ENCODED));
}
/**
@@ -147,13 +113,14 @@ class PropertyBasedArtifactUrlHandlerTest {
final String testHost = "ddi.host.com";
final UrlProtocol proto = new UrlProtocol();
- proto.setRef("{protocolRequest}://{hostname}:{port}/fws/{tenant}/{targetIdBase62}/{artifactIdBase62}");
+ proto.setRef("{protocolRequest}://{hostname}:{port}/fws/{tenant}/{controllerId}/{artifactFileName}");
properties.getProtocols().put("download-http", proto);
- assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DDI, new URI("https://" + testHost))).containsExactly(
+ assertThat(urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DDI,
+ new URI("https://" + testHost))).containsExactly(
new ArtifactUrl(
"http".toUpperCase(), "download-http",
- "https://localhost:8080/fws/" + TENANT + "/" + TARGET_ID_BASE62 + "/" + ARTIFACT_ID_BASE62));
+ "https://localhost:8080/fws/" + TENANT + "/" + CONTROLLER_ID + "/" + FILENAME_ENCODED));
}
/**
@@ -162,21 +129,23 @@ class PropertyBasedArtifactUrlHandlerTest {
@Test
void urlGenerationWithPortFromRequest() throws URISyntaxException {
final UrlProtocol proto = new UrlProtocol();
- proto.setRef("{protocol}://{hostname}:{portRequest}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}");
+ proto.setRef(
+ "{protocol}://{hostname}:{portRequest}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}");
properties.getProtocols().put("download-http", proto);
- assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DDI, new URI("http://anotherHost.com:8083"))).containsExactly(
+ assertThat(urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DDI,
+ new URI("http://anotherHost.com:8083"))).containsExactly(
new ArtifactUrl(
"http".toUpperCase(), "download-http",
"http://localhost:8083/" + TENANT + "/controller/v1/" +
- CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODE));
+ CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODED));
- assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DMF)).containsExactly(
+ assertThat(urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DMF)).containsExactly(
new ArtifactUrl(
"http".toUpperCase(), "download-http",
"http://localhost:8080/" + TENANT + "/controller/v1/" +
- CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODE));
+ CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODED));
}
/**
@@ -186,17 +155,16 @@ class PropertyBasedArtifactUrlHandlerTest {
void urlGenerationWithPortFromRequestForHttps() throws URISyntaxException {
final String protocol = "https";
final UrlProtocol proto = new UrlProtocol();
- proto.setRef("{protocolRequest}://{hostnameRequest}:{portRequest}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}");
+ proto.setRef(
+ "{protocolRequest}://{hostnameRequest}:{portRequest}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}");
proto.setProtocol(protocol);
properties.getProtocols().put("download-http", proto);
final URI uri = new URI(protocol + "://anotherHost.com");
- assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DDI, uri)).containsExactly(
+ assertThat(urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DDI, uri)).containsExactly(
new ArtifactUrl(
protocol.toUpperCase(), "download-http",
- uri + "/" + TENANT + "/controller/v1/" +
- CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODE));
-
+ uri + "/" + TENANT + "/controller/v1/" + CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODED));
}
/**
@@ -206,20 +174,22 @@ class PropertyBasedArtifactUrlHandlerTest {
void urlGenerationWithDomainFromRequest() throws URISyntaxException {
final UrlProtocol proto = new UrlProtocol();
proto.setHostname("host.bumlux.net");
- proto.setRef("{protocol}://{domainRequest}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}");
+ proto.setRef(
+ "{protocol}://{domainRequest}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}");
properties.getProtocols().put("download-http", proto);
- assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DDI, new URI("http://anotherHost.com:8083"))).containsExactly(
+ assertThat(urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DDI,
+ new URI("http://anotherHost.com:8083"))).containsExactly(
new ArtifactUrl(
"http".toUpperCase(), "download-http",
"http://host.com/" + TENANT + "/controller/v1/" +
- CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODE));
+ CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODED));
- assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DMF)).containsExactly(
+ assertThat(urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DMF)).containsExactly(
new ArtifactUrl(
"http".toUpperCase(), "download-http",
"http://host.bumlux.net/" + TENANT + "/controller/v1/" +
- CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODE));
+ CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODED));
}
}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/repository/artifact/urlhandler/Base62UtilTest.java b/hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/repository/artifact/urlhandler/Base62UtilTest.java
deleted file mode 100644
index 3d2028414..000000000
--- a/hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/repository/artifact/urlhandler/Base62UtilTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * Copyright (c) 2025 Contributors to the Eclipse Foundation
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.eclipse.hawkbit.repository.artifact.urlhandler;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.junit.jupiter.api.Test;
-
-/**
- * Feature: Unit Tests - Artifact URL Handler
- * Story: Base62 Utility tests
- */
-class Base62UtilTest {
-
- /**
- * Convert Base10 numbers to Base62 ASCII strings.
- */
- @Test
- void fromBase10() {
- assertThat(Base62Util.fromBase10(0L)).isEqualTo("0");
- assertThat(Base62Util.fromBase10(11L)).isEqualTo("B");
- assertThat(Base62Util.fromBase10(36L)).isEqualTo("a");
- assertThat(Base62Util.fromBase10(999L)).isEqualTo("G7");
- }
-
- /**
- * Convert Base62 ASCII strings to Base10 numbers.
- */
- @Test
- void toBase10() {
- assertThat(Base62Util.toBase10("0")).isZero();
- assertThat(Base62Util.toBase10("B")).isEqualTo(11);
- assertThat(Base62Util.toBase10("a")).isEqualTo(36L);
- assertThat(Base62Util.toBase10("G7")).isEqualTo(999L);
- }
-}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/repository/artifact/urlhandler/URLPlaceholderTest.java b/hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/repository/artifact/urlhandler/URLPlaceholderTest.java
deleted file mode 100644
index 1c3833631..000000000
--- a/hawkbit-artifact/hawkbit-artifact-api/src/test/java/org/eclipse/hawkbit/repository/artifact/urlhandler/URLPlaceholderTest.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/**
- * Copyright (c) 2025 Contributors to the Eclipse Foundation
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.eclipse.hawkbit.repository.artifact.urlhandler;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.junit.jupiter.api.Test;
-
-/**
- * Feature: Unit Tests - Artifact URL Handler
- * Story: URL placeholder tests
- */
-class URLPlaceholderTest {
-
- private final URLPlaceholder.SoftwareData softwareData;
- private final URLPlaceholder placeholder;
-
- public URLPlaceholderTest() {
- this.softwareData = new URLPlaceholder.SoftwareData(1L, "file.txt", 123L, "someHash123");
- this.placeholder = new URLPlaceholder("SuperCorp", 123L, "Super-1", 1L, softwareData);
- }
-
- /**
- * Same object should be equal
- */
- @Test
- // Exception squid:S5785 - JUnit assertTrue/assertFalse should be simplified to the corresponding dedicated assertion
- // Need to test the equals method and need to bypass magic logic in utility classes
- @SuppressWarnings({ "squid:S5838" })
- void sameObjectShouldBeEqual() {
- assertThat(softwareData.equals(softwareData)).isTrue();
- assertThat(placeholder.equals(placeholder)).isTrue();
- }
-
- /**
- * Different object should not be equal
- */
- @Test
- @SuppressWarnings({ "squid:S5838" })
- void differentObjectShouldNotBeEqual() {
- final URLPlaceholder.SoftwareData softwareData2 = new URLPlaceholder.SoftwareData(2L, "file.txt", 123L, "someHash123");
- final URLPlaceholder placeholder2 = new URLPlaceholder("SuperCorp", 123L, "Super-2", 2L, softwareData2);
- final URLPlaceholder placeholderWithOtherSoftwareData = new URLPlaceholder(placeholder.getTenant(),
- placeholder.getTenantId(), placeholder.getControllerId(), placeholder.getTargetId(), softwareData2);
- assertThat(placeholder.equals(placeholder2)).isFalse();
- assertThat(placeholder2.equals(placeholder)).isFalse();
- assertThat(softwareData.equals(softwareData2)).isFalse();
- assertThat(softwareData2.equals(softwareData)).isFalse();
- assertThat(placeholder.equals(placeholderWithOtherSoftwareData)).isFalse();
- }
-
- /**
- * Different objects with same properties should be equal
- */
- @Test
- void differentObjectsWithSamePropertiesShouldBeEqual() {
- final URLPlaceholder placeholderWithSameProperties = new URLPlaceholder(placeholder.getTenant(), placeholder.getTenantId(),
- placeholder.getControllerId(), placeholder.getTargetId(), softwareData);
- assertThat(placeholder).isEqualTo(placeholderWithSameProperties);
- assertThat(placeholderWithSameProperties).isEqualTo(placeholder);
- }
-
- /**
- * Should not equal null
- */
- @Test
- // Exception squid:S5785 - JUnit assertTrue/assertFalse should be simplified to
- // the corresponding dedicated assertion
- // Need to test the equals method and need to bypass magic logic in utility
- // classes
- @SuppressWarnings({ "squid:S5838" })
- void shouldNotEqualNull() {
- assertThat(placeholder.equals(null)).isFalse();
- assertThat(softwareData.equals(null)).isFalse();
- }
-
- /**
- * HashCode should not change
- */
- @Test
- void hashCodeShouldNotChange() {
- final URLPlaceholder placeholderWithSameProperties = new URLPlaceholder(placeholder.getTenant(), placeholder.getTenantId(),
- placeholder.getControllerId(), placeholder.getTargetId(), softwareData);
- assertThat(placeholder).hasSameHashCodeAs(placeholder).hasSameHashCodeAs(placeholderWithSameProperties);
- }
-}
diff --git a/hawkbit-artifact/hawkbit-artifact-fs/README.md b/hawkbit-artifact/hawkbit-artifact-fs/README.md
new file mode 100644
index 000000000..251483373
--- /dev/null
+++ b/hawkbit-artifact/hawkbit-artifact-fs/README.md
@@ -0,0 +1,8 @@
+Eclipse.IoT hawkBit - Artifact Repository File System
+===
+This module contains the implementation of [ArtifactStorage](../hawkbit-artifact-api/src/main/java/org/eclipse/hawkbit/artifact/ArtifactStorage.java) based on the file-system.
+It's a very convenient and easy implementation of storing the artifact binaries into the file-system based on the SHA-1 hash naming.
+
+Due to the limit of many file-systems of files within one directory, the files
+are stored in different sub-directories based on the last four digits of the
+SHA1-hash `/basepath/[two digit sha1]/[two digit sha1/sha1-hash-filename]`.
diff --git a/hawkbit-artifact/hawkbit-artifact-repository-filesystem/pom.xml b/hawkbit-artifact/hawkbit-artifact-fs/pom.xml
similarity index 95%
rename from hawkbit-artifact/hawkbit-artifact-repository-filesystem/pom.xml
rename to hawkbit-artifact/hawkbit-artifact-fs/pom.xml
index d340b6e5a..76271258b 100644
--- a/hawkbit-artifact/hawkbit-artifact-repository-filesystem/pom.xml
+++ b/hawkbit-artifact/hawkbit-artifact-fs/pom.xml
@@ -18,7 +18,7 @@
${revision}
- hawkbit-artifact-repository-filesystem
+ hawkbit-artifact-fshawkBit :: Artifact :: Filesystem Artifact Repository
diff --git a/hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/repository/artifact/ArtifactFilesystemProperties.java b/hawkbit-artifact/hawkbit-artifact-fs/src/main/java/org/eclipse/hawkbit/artifact/fs/FileArtifactProperties.java
similarity index 88%
rename from hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/repository/artifact/ArtifactFilesystemProperties.java
rename to hawkbit-artifact/hawkbit-artifact-fs/src/main/java/org/eclipse/hawkbit/artifact/fs/FileArtifactProperties.java
index 3e3f4005a..a89d8cac8 100644
--- a/hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/repository/artifact/ArtifactFilesystemProperties.java
+++ b/hawkbit-artifact/hawkbit-artifact-fs/src/main/java/org/eclipse/hawkbit/artifact/fs/FileArtifactProperties.java
@@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.repository.artifact;
+package org.eclipse.hawkbit.artifact.fs;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -17,7 +17,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
*/
@Data
@ConfigurationProperties("org.eclipse.hawkbit.repository.file")
-public class ArtifactFilesystemProperties {
+public class FileArtifactProperties {
/**
* The base-path of the directory to store the artifacts.
diff --git a/hawkbit-artifact/hawkbit-artifact-fs/src/main/java/org/eclipse/hawkbit/artifact/fs/FileArtifactStorage.java b/hawkbit-artifact/hawkbit-artifact-fs/src/main/java/org/eclipse/hawkbit/artifact/fs/FileArtifactStorage.java
new file mode 100644
index 000000000..92cd7a39a
--- /dev/null
+++ b/hawkbit-artifact/hawkbit-artifact-fs/src/main/java/org/eclipse/hawkbit/artifact/fs/FileArtifactStorage.java
@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.hawkbit.artifact.fs;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.apache.commons.io.FileUtils;
+import org.eclipse.hawkbit.artifact.AbstractArtifactStorage;
+import org.eclipse.hawkbit.artifact.ArtifactStorage;
+import org.eclipse.hawkbit.artifact.exception.ArtifactBinaryNotFoundException;
+import org.eclipse.hawkbit.artifact.exception.ArtifactStoreException;
+import org.eclipse.hawkbit.artifact.model.ArtifactHashes;
+import org.eclipse.hawkbit.artifact.model.StoredArtifactInfo;
+import org.springframework.validation.annotation.Validated;
+
+/**
+ * Implementation of the {@link ArtifactStorage} to store artifacts on the file-system. The files are stored by their SHA1 hash of the
+ * artifact binary. Duplicate files with the same SHA1 hash will only be stored once.
+ *
+ * All files are stored flat in one base directory configured in the {@link FileArtifactProperties#getPath()}.
+ *
+ * Due to the limit of many file-systems of files within one directory, the files are stored in different subdirectories based on the last four
+ * digits of the SHA1-hash {@code (/basepath/[two digit sha1]/[two digit sha1])}.
+ */
+@Validated
+public class FileArtifactStorage extends AbstractArtifactStorage {
+
+ private final FileArtifactProperties artifactResourceProperties;
+
+ public FileArtifactStorage(final FileArtifactProperties artifactResourceProperties) {
+ this.artifactResourceProperties = artifactResourceProperties;
+ }
+
+ @Override
+ public void deleteBySha1(final String tenant, final String sha1) {
+ FileUtils.deleteQuietly(getFile(tenant, sha1));
+ }
+
+ @Override
+ public InputStream getBySha1(final String tenant, final String sha1) {
+ final File file = getFile(tenant, sha1);
+ if (!file.exists()) {
+ throw new ArtifactBinaryNotFoundException(sha1);
+ }
+ try {
+ return new BufferedInputStream(new FileInputStream(file));
+ } catch (final FileNotFoundException e) {
+ throw new ArtifactBinaryNotFoundException(sha1);
+ }
+ }
+
+ @Override
+ public void deleteByTenant(final String tenant) {
+ FileUtils.deleteQuietly(Paths.get(artifactResourceProperties.getPath(), sanitizeTenant(tenant)).toFile());
+ }
+
+ @Override
+ public boolean existsBySha1(final String tenant, final String sha1) {
+ return getFile(tenant, sha1).exists();
+ }
+
+ @Override
+ protected StoredArtifactInfo store(final String tenant, final ArtifactHashes base16Hashes, final String contentType, final String tempFile)
+ throws IOException {
+ final File file = new File(tempFile);
+ final File fileSHA1Naming = getFile(tenant, base16Hashes.sha1());
+ if (fileSHA1Naming.exists()) {
+ FileUtils.deleteQuietly(file);
+ } else {
+ Files.move(file.toPath(), fileSHA1Naming.toPath());
+ }
+
+ return new StoredArtifactInfo(contentType, fileSHA1Naming.length(), base16Hashes);
+ }
+
+ private File getFile(final String tenant, final String sha1) {
+ // ensure that the sha1 is not a path traversal attack
+ if (sha1.indexOf('/') >= 0 || sha1.indexOf('\\') >= 0) {
+ throw new IllegalArgumentException("Invalid SHA-1 hash: " + sha1);
+ }
+
+ final File artifactDirectory = getSha1DirectoryPath(tenant, sha1).toFile();
+ if (!artifactDirectory.isDirectory()) {
+ if (artifactDirectory.isFile()) {
+ throw new ArtifactStoreException(artifactDirectory + " is a file, but a directory is required.");
+ } else if (!artifactDirectory.mkdirs()) {
+ throw new ArtifactStoreException("Fail to create directories: " + artifactDirectory);
+ }
+ }
+ return new File(artifactDirectory, sha1);
+ }
+
+ private Path getSha1DirectoryPath(final String tenant, final String sha1) {
+ final int length = sha1.length();
+ final String folder1 = sha1.substring(length - 4, length - 2);
+ final String folder2 = sha1.substring(length - 2, length);
+ return Paths.get(artifactResourceProperties.getPath(), sanitizeTenant(tenant), folder1, folder2);
+ }
+}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/autoconfigure/repository/artifact/filesystem/ArtifactFilesystemConfiguration.java b/hawkbit-artifact/hawkbit-artifact-fs/src/main/java/org/eclipse/hawkbit/autoconfigure/artifact/fs/FileArtifactStorageConfiguration.java
similarity index 50%
rename from hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/autoconfigure/repository/artifact/filesystem/ArtifactFilesystemConfiguration.java
rename to hawkbit-artifact/hawkbit-artifact-fs/src/main/java/org/eclipse/hawkbit/autoconfigure/artifact/fs/FileArtifactStorageConfiguration.java
index ea9ba64a4..e7c85fb99 100644
--- a/hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/autoconfigure/repository/artifact/filesystem/ArtifactFilesystemConfiguration.java
+++ b/hawkbit-artifact/hawkbit-artifact-fs/src/main/java/org/eclipse/hawkbit/autoconfigure/artifact/fs/FileArtifactStorageConfiguration.java
@@ -7,30 +7,30 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.autoconfigure.repository.artifact.filesystem;
+package org.eclipse.hawkbit.autoconfigure.artifact.fs;
-import org.eclipse.hawkbit.repository.artifact.ArtifactFilesystemProperties;
-import org.eclipse.hawkbit.repository.artifact.ArtifactFilesystemRepository;
-import org.eclipse.hawkbit.repository.artifact.ArtifactRepository;
+import org.eclipse.hawkbit.artifact.ArtifactStorage;
+import org.eclipse.hawkbit.artifact.fs.FileArtifactProperties;
+import org.eclipse.hawkbit.artifact.fs.FileArtifactStorage;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
- * Configuration for the {@link ArtifactFilesystemRepository}.
+ * Configuration for the {@link FileArtifactStorage}.
*/
@Configuration
-@EnableConfigurationProperties(ArtifactFilesystemProperties.class)
-public class ArtifactFilesystemConfiguration {
+@EnableConfigurationProperties(FileArtifactProperties.class)
+public class FileArtifactStorageConfiguration {
/**
* @param artifactFilesystemProperties the artifact file system properties
- * @return Default {@link ArtifactRepository} implementation.
+ * @return Default {@link ArtifactStorage} implementation.
*/
@Bean
@ConditionalOnMissingBean
- public ArtifactRepository artifactRepository(final ArtifactFilesystemProperties artifactFilesystemProperties) {
- return new ArtifactFilesystemRepository(artifactFilesystemProperties);
+ public ArtifactStorage artifactRepository(final FileArtifactProperties artifactFilesystemProperties) {
+ return new FileArtifactStorage(artifactFilesystemProperties);
}
}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-fs/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/hawkbit-artifact/hawkbit-artifact-fs/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 000000000..8062fc010
--- /dev/null
+++ b/hawkbit-artifact/hawkbit-artifact-fs/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.eclipse.hawkbit.autoconfigure.artifact.fs.FileArtifactStorageConfiguration
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/test/java/org/eclipse/hawkbit/repository/artifact/ArtifactFilesystemRepositoryTest.java b/hawkbit-artifact/hawkbit-artifact-fs/src/test/java/org/eclipse/hawkbit/artifact/fs/FileArtifactStorageTest.java
similarity index 74%
rename from hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/test/java/org/eclipse/hawkbit/repository/artifact/ArtifactFilesystemRepositoryTest.java
rename to hawkbit-artifact/hawkbit-artifact-fs/src/test/java/org/eclipse/hawkbit/artifact/fs/FileArtifactStorageTest.java
index 282d4cc79..dc4d5cc1a 100644
--- a/hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/test/java/org/eclipse/hawkbit/repository/artifact/ArtifactFilesystemRepositoryTest.java
+++ b/hawkbit-artifact/hawkbit-artifact-fs/src/test/java/org/eclipse/hawkbit/artifact/fs/FileArtifactStorageTest.java
@@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.eclipse.hawkbit.repository.artifact;
+package org.eclipse.hawkbit.artifact.fs;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -21,8 +21,9 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.assertj.core.api.Assertions;
-import org.eclipse.hawkbit.repository.artifact.exception.ArtifactBinaryNotFoundException;
-import org.eclipse.hawkbit.repository.artifact.model.AbstractDbArtifact;
+import org.eclipse.hawkbit.artifact.AbstractArtifactStorage;
+import org.eclipse.hawkbit.artifact.exception.ArtifactBinaryNotFoundException;
+import org.eclipse.hawkbit.artifact.model.StoredArtifactInfo;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -32,19 +33,19 @@ import org.junit.jupiter.api.Test;
* Story: Test storing artifact binaries in the file-system
*/
@Slf4j
-class ArtifactFilesystemRepositoryTest {
+class FileArtifactStorageTest {
private static final String TENANT = "test_tenant";
- private static ArtifactFilesystemProperties artifactResourceProperties;
- private static ArtifactFilesystemRepository artifactFilesystemRepository;
+ private static FileArtifactProperties artifactResourceProperties;
+ private static FileArtifactStorage artifactFilesystemRepository;
@BeforeAll
static void setup() {
- artifactResourceProperties = new ArtifactFilesystemProperties();
- artifactResourceProperties.setPath(AbstractArtifactRepository.createTempFile(true).toString());
+ artifactResourceProperties = new FileArtifactProperties();
+ artifactResourceProperties.setPath(AbstractArtifactStorage.createTempFile(true).toString());
- artifactFilesystemRepository = new ArtifactFilesystemRepository(artifactResourceProperties);
+ artifactFilesystemRepository = new FileArtifactStorage(artifactResourceProperties);
}
@AfterAll
@@ -64,10 +65,10 @@ class ArtifactFilesystemRepositoryTest {
@Test
void storeSuccessfully() throws IOException {
final byte[] fileContent = randomBytes();
- final AbstractDbArtifact artifact = storeRandomArtifact(fileContent);
+ final StoredArtifactInfo artifact = storeRandomArtifact(fileContent);
final byte[] readContent = new byte[fileContent.length];
- IOUtils.read(artifact.getFileInputStream(), readContent);
+ IOUtils.read(artifactFilesystemRepository.getBySha1(TENANT, artifact.getHashes().sha1()), readContent);
assertThat(readContent).isEqualTo(fileContent);
}
@@ -77,9 +78,9 @@ class ArtifactFilesystemRepositoryTest {
@Test
void getStoredArtifactBasedOnSHA1Hash() throws IOException {
final byte[] fileContent = randomBytes();
- final AbstractDbArtifact artifact = storeRandomArtifact(fileContent);
+ final StoredArtifactInfo artifact = storeRandomArtifact(fileContent);
- assertThat(artifactFilesystemRepository.getBySha1(TENANT, artifact.getHashes().getSha1())).isNotNull();
+ assertThat(artifactFilesystemRepository.getBySha1(TENANT, artifact.getHashes().sha1())).isNotNull();
}
/**
@@ -87,10 +88,10 @@ class ArtifactFilesystemRepositoryTest {
*/
@Test
void deleteStoredArtifactBySHA1Hash() throws IOException {
- final AbstractDbArtifact artifact = storeRandomArtifact(randomBytes());
- artifactFilesystemRepository.deleteBySha1(TENANT, artifact.getHashes().getSha1());
+ final StoredArtifactInfo artifact = storeRandomArtifact(randomBytes());
+ artifactFilesystemRepository.deleteBySha1(TENANT, artifact.getHashes().sha1());
- final String sha1Hash = artifact.getHashes().getSha1();
+ final String sha1Hash = artifact.getHashes().sha1();
assertThatExceptionOfType(ArtifactBinaryNotFoundException.class)
.isThrownBy(() -> artifactFilesystemRepository.getBySha1(TENANT, sha1Hash));
}
@@ -100,10 +101,10 @@ class ArtifactFilesystemRepositoryTest {
*/
@Test
void deleteStoredArtifactOfTenant() throws IOException {
- final AbstractDbArtifact artifact = storeRandomArtifact(randomBytes());
+ final StoredArtifactInfo artifact = storeRandomArtifact(randomBytes());
artifactFilesystemRepository.deleteByTenant(TENANT);
- final String sha1Hash = artifact.getHashes().getSha1();
+ final String sha1Hash = artifact.getHashes().sha1();
assertThatExceptionOfType(ArtifactBinaryNotFoundException.class)
.isThrownBy(() -> artifactFilesystemRepository.getBySha1(TENANT, sha1Hash));
}
@@ -119,9 +120,9 @@ class ArtifactFilesystemRepositoryTest {
Assertions.fail("did not expect an exception while deleting a file which does not exists");
}
- final AbstractDbArtifact artifact = storeRandomArtifact(randomBytes());
+ final StoredArtifactInfo artifact = storeRandomArtifact(randomBytes());
try {
- artifactFilesystemRepository.deleteBySha1("tenantWhichDoesNotExist", artifact.getHashes().getSha1());
+ artifactFilesystemRepository.deleteBySha1("tenantWhichDoesNotExist", artifact.getHashes().sha1());
} catch (final Exception e) {
Assertions.fail("did not expect an exception while deleting a file which does not exists");
}
@@ -133,7 +134,7 @@ class ArtifactFilesystemRepositoryTest {
return randomBytes;
}
- private AbstractDbArtifact storeRandomArtifact(final byte[] fileContent) throws IOException {
+ private StoredArtifactInfo storeRandomArtifact(final byte[] fileContent) throws IOException {
try (final ByteArrayInputStream inputStream = new ByteArrayInputStream(fileContent)) {
return artifactFilesystemRepository.store(TENANT, inputStream, "filename.tmp", "application/txt", null);
}
diff --git a/hawkbit-artifact/hawkbit-artifact-repository-filesystem/README.md b/hawkbit-artifact/hawkbit-artifact-repository-filesystem/README.md
deleted file mode 100644
index 44d133a4e..000000000
--- a/hawkbit-artifact/hawkbit-artifact-repository-filesystem/README.md
+++ /dev/null
@@ -1,18 +0,0 @@
-# Eclipse.IoT hawkBit - Artifact Repository File System
-
-This module contains the implementation of the `ArtifactRepository` based on the file-system.
-It's a very convenient and easy implementation of storing the artifact binaries into the file-system based on the SHA-1
-hash naming.
-
-Due the limit of many file-systems of files within one directory, the files
-are stored in different sub-directories based on the last four digits of the
-SHA1-hash `/basepath/[two digit sha1]/[two digit sha1/sha1-hash-filename]`.
-
-# Compile
-
-#### Build hawkbit-artifact-repository-filesystem
-
-```
-$ cd hawkbit/hawkbit-artifact-repository-filesystem
-$ mvn clean install
-```
diff --git a/hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/repository/artifact/ArtifactFileNotFoundException.java b/hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/repository/artifact/ArtifactFileNotFoundException.java
deleted file mode 100644
index 63acf7733..000000000
--- a/hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/repository/artifact/ArtifactFileNotFoundException.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * Copyright (c) 2025 Contributors to the Eclipse Foundation
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.eclipse.hawkbit.repository.artifact;
-
-/**
- * Exception thrown in case that the artifact could not be read.
- */
-public class ArtifactFileNotFoundException extends RuntimeException {
-
- /**
- * Creates the Exception from its cause
- *
- * @param cause the original exception
- */
- public ArtifactFileNotFoundException(final Exception cause) {
- super(cause);
- }
-}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/repository/artifact/ArtifactFilesystem.java b/hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/repository/artifact/ArtifactFilesystem.java
deleted file mode 100644
index 0133bad22..000000000
--- a/hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/repository/artifact/ArtifactFilesystem.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * Copyright (c) 2025 Contributors to the Eclipse Foundation
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.eclipse.hawkbit.repository.artifact;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.util.Objects;
-
-import jakarta.validation.constraints.NotNull;
-
-import lombok.EqualsAndHashCode;
-import org.eclipse.hawkbit.repository.artifact.model.AbstractDbArtifact;
-import org.eclipse.hawkbit.repository.artifact.model.DbArtifactHash;
-
-/**
- * {@link AbstractDbArtifact} implementation which dynamically creates a {@link FileInputStream} on calling {@link #getFileInputStream()}.
- */
-@EqualsAndHashCode(callSuper = true)
-public class ArtifactFilesystem extends AbstractDbArtifact {
-
- private final File file;
-
- public ArtifactFilesystem(
- @NotNull final File file, @NotNull final String artifactId,
- @NotNull final DbArtifactHash hashes, final Long size,
- final String contentType) {
- super(artifactId, hashes, size, contentType);
- this.file = Objects.requireNonNull(file, "Artifact file may not be null");
- }
-
- // suppress warning, this InputStream needs to be closed by the caller, this
- // cannot be closed in this method
- @SuppressWarnings("squid:S2095")
- @Override
- public InputStream getFileInputStream() {
- try {
- return new BufferedInputStream(new FileInputStream(file));
- } catch (final FileNotFoundException e) {
- throw new ArtifactFileNotFoundException(e);
- }
- }
-}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/repository/artifact/ArtifactFilesystemRepository.java b/hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/repository/artifact/ArtifactFilesystemRepository.java
deleted file mode 100644
index 175817791..000000000
--- a/hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/repository/artifact/ArtifactFilesystemRepository.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/**
- * Copyright (c) 2025 Contributors to the Eclipse Foundation
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.eclipse.hawkbit.repository.artifact;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Optional;
-
-import org.apache.commons.io.FileUtils;
-import org.eclipse.hawkbit.repository.artifact.exception.ArtifactBinaryNotFoundException;
-import org.eclipse.hawkbit.repository.artifact.model.AbstractDbArtifact;
-import org.eclipse.hawkbit.repository.artifact.model.DbArtifactHash;
-import org.springframework.validation.annotation.Validated;
-
-/**
- * Implementation of the {@link ArtifactRepository} to store artifacts on the
- * file-system. The files are stored by their SHA1 hash of the artifact binary.
- * Duplicate files with the same SHA1 hash will only be stored once.
- *
- * All files are stored flat in one base directory configured in the
- * {@link ArtifactFilesystemProperties#getPath()}.
- *
- * Due to the limit of many file-systems of files within one directory, the files
- * are stored in different subdirectories based on the last four digits of the
- * SHA1-hash {@code (/basepath/[two digit sha1]/[two digit sha1])}.
- */
-@Validated
-public class ArtifactFilesystemRepository extends AbstractArtifactRepository {
-
- private final ArtifactFilesystemProperties artifactResourceProperties;
-
- /**
- * Constructor.
- *
- * @param artifactResourceProperties the properties which holds the necessary configuration for the file-system repository
- */
- public ArtifactFilesystemRepository(final ArtifactFilesystemProperties artifactResourceProperties) {
- this.artifactResourceProperties = artifactResourceProperties;
- }
-
- @Override
- public void deleteBySha1(final String tenant, final String sha1Hash) {
- FileUtils.deleteQuietly(getFile(tenant, sha1Hash));
- }
-
- @Override
- public AbstractDbArtifact getBySha1(final String tenant, final String sha1) {
- final File file = getFile(tenant, sha1);
- if (!file.exists()) {
- throw new ArtifactBinaryNotFoundException(sha1);
- }
-
- return new ArtifactFilesystem(file, sha1, new DbArtifactHash(sha1, null, null), file.length(), null);
- }
-
- @Override
- public void deleteByTenant(final String tenant) {
- FileUtils.deleteQuietly(Paths.get(artifactResourceProperties.getPath(), sanitizeTenant(tenant)).toFile());
- }
-
- @Override
- public boolean existsBySha1(final String tenant, final String sha1) {
- return getFile(tenant, sha1).exists();
- }
-
- @Override
- protected AbstractDbArtifact store(final String tenant, final DbArtifactHash base16Hashes, final String contentType, final String tempFile)
- throws IOException {
- final File file = new File(tempFile);
- return renameFileToSHA1Naming(
- tenant, file,
- new ArtifactFilesystem(file, base16Hashes.getSha1(), base16Hashes, file.length(), contentType));
- }
-
- private ArtifactFilesystem renameFileToSHA1Naming(final String tenant, final File file,
- final AbstractDbArtifact artifact) throws IOException {
- final File fileSHA1Naming = getFile(tenant, artifact.getHashes().getSha1());
- if (fileSHA1Naming.exists()) {
- FileUtils.deleteQuietly(file);
- } else {
- Files.move(file.toPath(), fileSHA1Naming.toPath());
- }
-
- return new ArtifactFilesystem(
- fileSHA1Naming, artifact.getArtifactId(), artifact.getHashes(), artifact.getSize(), artifact.getContentType());
- }
-
- private File getFile(final String tenant, final String sha1) {
- // ensure that the sha1 is not a path traversal attack
- if (sha1.indexOf('/') >= 0 || sha1.indexOf('\\') >= 0) {
- throw new IllegalArgumentException("Invalid sha1 hash: " + sha1);
- }
-
- final File aritfactDirectory = getSha1DirectoryPath(tenant, sha1).toFile();
- aritfactDirectory.mkdirs();
- return new File(aritfactDirectory, sha1);
- }
-
- private Path getSha1DirectoryPath(final String tenant, final String sha1) {
- final int length = sha1.length();
- final String folder1 = sha1.substring(length - 4, length - 2);
- final String folder2 = sha1.substring(length - 2, length);
- return Paths.get(artifactResourceProperties.getPath(), sanitizeTenant(tenant), folder1, folder2);
- }
-}
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
deleted file mode 100644
index fe8092964..000000000
--- a/hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ /dev/null
@@ -1 +0,0 @@
-org.eclipse.hawkbit.autoconfigure.repository.artifact.filesystem.ArtifactFilesystemConfiguration
\ No newline at end of file
diff --git a/hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/test/java/org/eclipse/hawkbit/repository/artifact/ArtifactFilesystemTest.java b/hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/test/java/org/eclipse/hawkbit/repository/artifact/ArtifactFilesystemTest.java
deleted file mode 100644
index f5dae9c95..000000000
--- a/hawkbit-artifact/hawkbit-artifact-repository-filesystem/src/test/java/org/eclipse/hawkbit/repository/artifact/ArtifactFilesystemTest.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * Copyright (c) 2025 Contributors to the Eclipse Foundation
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.eclipse.hawkbit.repository.artifact;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-
-import org.apache.commons.io.IOUtils;
-import org.eclipse.hawkbit.repository.artifact.model.DbArtifactHash;
-import org.junit.jupiter.api.Test;
-
-/**
- * Feature: Unit Tests - Artifact File System Repository
- * Story: Test storing artifact binaries in the file-system
- */
-class ArtifactFilesystemTest {
-
- /**
- * Verifies that an exception is thrown on opening an InputStream when file does not exists
- */
- @Test
- void getInputStreamOfNonExistingFileThrowsException() {
- final File file = new File("fileWhichTotalDoesNotExists");
- final ArtifactFilesystem underTest = new ArtifactFilesystem(
- file, "fileWhichTotalDoesNotExists",
- new DbArtifactHash("1", "2", "3"), 0L, null);
- assertThatThrownBy(underTest::getFileInputStream)
- .isInstanceOf(ArtifactFileNotFoundException.class)
- .hasCauseInstanceOf(FileNotFoundException.class);
- }
-
- /**
- * Verifies that an InputStream can be opened if file exists
- */
- @Test
- void getInputStreamOfExistingFile() throws IOException {
- final ArtifactFilesystem underTest = new ArtifactFilesystem(
- AbstractArtifactRepository.createTempFile(false), ArtifactFilesystemTest.class.getSimpleName(),
- new DbArtifactHash("1", "2", "3"), 0L, null);
- assertThat(IOUtils.read(underTest.getFileInputStream(), new byte[16])).isZero();
- }
-}
\ No newline at end of file
diff --git a/hawkbit-artifact/pom.xml b/hawkbit-artifact/pom.xml
index eeaba7752..eea31afaa 100644
--- a/hawkbit-artifact/pom.xml
+++ b/hawkbit-artifact/pom.xml
@@ -24,6 +24,6 @@
hawkbit-artifact-api
- hawkbit-artifact-repository-filesystem
+ hawkbit-artifact-fs
\ No newline at end of file
diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/artifact/ArtifactUrlHandlerAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/artifact/PropertyBasedArtifactUrlResolverAutoConfiguration.java
similarity index 52%
rename from hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/artifact/ArtifactUrlHandlerAutoConfiguration.java
rename to hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/artifact/PropertyBasedArtifactUrlResolverAutoConfiguration.java
index 27cccb8cb..bf3da754d 100644
--- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/artifact/ArtifactUrlHandlerAutoConfiguration.java
+++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/artifact/PropertyBasedArtifactUrlResolverAutoConfiguration.java
@@ -9,9 +9,9 @@
*/
package org.eclipse.hawkbit.autoconfigure.artifact;
-import org.eclipse.hawkbit.repository.artifact.urlhandler.ArtifactUrlHandler;
-import org.eclipse.hawkbit.repository.artifact.urlhandler.ArtifactUrlHandlerProperties;
-import org.eclipse.hawkbit.repository.artifact.urlhandler.PropertyBasedArtifactUrlHandler;
+import org.eclipse.hawkbit.artifact.urlresolver.ArtifactUrlResolver;
+import org.eclipse.hawkbit.artifact.urlresolver.PropertyBasedArtifactUrlResolver;
+import org.eclipse.hawkbit.artifact.urlresolver.PropertyBasedArtifactUrlResolverProperties;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -20,22 +20,18 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
/**
- * Autoconfiguration for {@link ArtifactUrlHandler} based on a properties.
+ * Autoconfiguration for {@link ArtifactUrlResolver} based on a properties.
*/
@Configuration
-@EnableConfigurationProperties({ ArtifactUrlHandlerProperties.class })
+@EnableConfigurationProperties({ PropertyBasedArtifactUrlResolverProperties.class })
@PropertySource("classpath:/hawkbit-artifactdl-defaults.properties")
-public class ArtifactUrlHandlerAutoConfiguration {
+public class PropertyBasedArtifactUrlResolverAutoConfiguration {
- /**
- * @param urlHandlerProperties for bean configuration
- * @return PropertyBasedArtifactUrlHandler bean
- */
@Bean
- @ConditionalOnMissingBean(ArtifactUrlHandler.class)
- PropertyBasedArtifactUrlHandler propertyBasedArtifactUrlHandler(
- final ArtifactUrlHandlerProperties urlHandlerProperties,
+ @ConditionalOnMissingBean(ArtifactUrlResolver.class)
+ PropertyBasedArtifactUrlResolver propertyBasedArtifactUrlHandler(
+ final PropertyBasedArtifactUrlResolverProperties urlHandlerProperties,
@Value("${server.servlet.context-path:}") final String contextPath) {
- return new PropertyBasedArtifactUrlHandler(urlHandlerProperties, contextPath);
+ return new PropertyBasedArtifactUrlResolver(urlHandlerProperties, contextPath);
}
}
\ No newline at end of file
diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/cache/CacheAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/cache/CacheAutoConfiguration.java
index 2e0464a20..48f493ee7 100644
--- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/cache/CacheAutoConfiguration.java
+++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/cache/CacheAutoConfiguration.java
@@ -10,8 +10,8 @@
package org.eclipse.hawkbit.autoconfigure.cache;
import com.github.benmanes.caffeine.cache.Caffeine;
-import org.eclipse.hawkbit.cache.TenancyCacheManager;
-import org.eclipse.hawkbit.cache.TenantAwareCacheManager;
+import org.eclipse.hawkbit.tenancy.cache.TenantCacheManager;
+import org.eclipse.hawkbit.tenancy.cache.TenantAwareCacheManager;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -27,7 +27,7 @@ import org.springframework.context.annotation.Primary;
* A configuration for configuring the spring {@link CacheManager} for specific multi-tenancy caching. The caches between
* tenants must not interfere each other.
*
- * This is done by providing a special {@link TenancyCacheManager} which generates a cache name included the current tenant.
+ * This is done by providing a special {@link TenantCacheManager} which generates a cache name included the current tenant.
*/
@Configuration
@EnableCaching
@@ -39,9 +39,8 @@ public class CacheAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@Primary
- TenancyCacheManager cacheManager(
- @Qualifier("directCacheManager") final CacheManager directCacheManager,
- final TenantAware tenantAware) {
+ TenantCacheManager cacheManager(
+ @Qualifier("directCacheManager") final CacheManager directCacheManager, final TenantAware tenantAware) {
return new TenantAwareCacheManager(directCacheManager, tenantAware);
}
@@ -63,9 +62,7 @@ public class CacheAutoConfiguration {
final CaffeineCacheManager cacheManager = new CaffeineCacheManager();
if (cacheProperties.getTtl() > 0) {
- final Caffeine
- *
*
* The request supports RFC7233 range requests.
*
@@ -59,16 +58,16 @@ public final class FileStreamingUtil {
* @param progressListener to write progress updates to
* @return http response
* @throws FileStreamingFailedException if streaming fails
- * @see https://tools.ietf.org
- * /html/rfc7233
+ * @see https://tools.ietf.org/html/rfc7233
*/
- public static ResponseEntity writeFileResponse(final DbArtifact artifact, final String filename,
+ public static ResponseEntity writeFileResponse(
+ final ArtifactStream artifact, final String filename,
final long lastModified, final HttpServletResponse response, final HttpServletRequest request,
final FileStreamingProgressListener progressListener) {
ResponseEntity result;
- final String etag = artifact.getHashes().getSha1();
+ final String etag = artifact.getSha1Hash();
final long length = artifact.getSize();
resetResponseExceptHeaders(response);
@@ -114,20 +113,21 @@ public final class FileStreamingUtil {
}
}
- // full request - no range
- if (ranges.isEmpty() || ranges.get(0).equals(full)) {
- log.debug("filename ({}) results into a full request: ", filename);
- result = handleFullFileRequest(artifact, filename, response, progressListener, full);
- }
- // standard range request
- else if (ranges.size() == 1) {
- log.debug("filename ({}) results into a standard range request: ", filename);
- result = handleStandardRangeRequest(artifact, filename, response, progressListener, ranges);
- }
- // multipart range request
- else {
- log.debug("filename ({}) results into a multipart range request: ", filename);
- result = handleMultipartRangeRequest(artifact, filename, response, progressListener, ranges);
+ try (final InputStream inputStream = artifact) {
+ // full request - no range
+ if (ranges.isEmpty() || ranges.get(0).equals(full)) {
+ log.debug("filename ({}) results into a full request: ", filename);
+ result = handleFullFileRequest(inputStream, filename, response, progressListener, full);
+ } else if (ranges.size() == 1) { // standard range request
+ log.debug("filename ({}) results into a standard range request: ", filename);
+ result = handleStandardRangeRequest(inputStream, filename, response, progressListener, ranges.get(0));
+ } else { // multipart range request
+ log.debug("filename ({}) results into a multipart range request: ", filename);
+ result = handleMultipartRangeRequest(inputStream, filename, response, progressListener, ranges);
+ }
+ } catch (final IOException e) {
+ log.error("streaming of file ({}) failed!", filename, e);
+ throw new FileStreamingFailedException(filename, e);
}
return result;
@@ -146,16 +146,15 @@ public final class FileStreamingUtil {
storedHeaders.forEach(response::addHeader);
}
- private static ResponseEntity handleFullFileRequest(final DbArtifact artifact, final String filename,
- final HttpServletResponse response, final FileStreamingProgressListener progressListener,
- final ByteRange full) {
- final ByteRange r = full;
- response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + r.getStart() + "-" + r.getEnd() + "/" + r.getTotal());
- response.setContentLengthLong(r.getLength());
+ private static ResponseEntity handleFullFileRequest(
+ final InputStream inputStream, final String filename, final HttpServletResponse response,
+ final FileStreamingProgressListener progressListener, final ByteRange full) {
+ response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + full.getStart() + "-" + full.getEnd() + "/" + full.getTotal());
+ response.setContentLengthLong(full.getLength());
- try (final InputStream from = artifact.getFileInputStream()) {
+ try {
final ServletOutputStream to = response.getOutputStream();
- copyStreams(from, to, progressListener, r.getStart(), r.getLength(), filename);
+ copyStreams(inputStream, to, progressListener, full.getStart(), full.getLength(), filename);
} catch (final IOException e) {
throw new FileStreamingFailedException("fullfileRequest " + filename, e);
}
@@ -214,28 +213,30 @@ public final class FileStreamingUtil {
}
}
- private static ResponseEntity handleMultipartRangeRequest(final DbArtifact artifact,
- final String filename, final HttpServletResponse response,
+ private static ResponseEntity handleMultipartRangeRequest(
+ final InputStream inputStream, final String filename, final HttpServletResponse response,
final FileStreamingProgressListener progressListener, final List ranges) {
-
+ ranges.sort((r1, r2) -> Long.compare(r1.getStart(), r2.getStart()));
response.setContentType("multipart/byteranges; boundary=" + ByteRange.MULTIPART_BOUNDARY);
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
try {
final ServletOutputStream to = response.getOutputStream();
- for (final ByteRange r : ranges) {
- try (final InputStream from = artifact.getFileInputStream()) {
-
- // Add multipart boundary and header fields for every range.
- to.println();
- to.println("--" + ByteRange.MULTIPART_BOUNDARY);
- to.println(HttpHeaders.CONTENT_RANGE + ": bytes " + r.getStart() + "-" + r.getEnd() + "/"
- + r.getTotal());
-
- // Copy single part range of multi part range.
- copyStreams(from, to, progressListener, r.getStart(), r.getLength(), filename);
+ long streamPos = 0;
+ for (final ByteRange range : ranges) {
+ if (streamPos > range.getStart()) {
+ throw new FileStreamingFailedException("Ranges are overlapping or not in order");
}
+
+ // Add multipart boundary and header fields for every range.
+ to.println();
+ to.println("--" + ByteRange.MULTIPART_BOUNDARY);
+ to.println(HttpHeaders.CONTENT_RANGE + ": bytes " + range.getStart() + "-" + range.getEnd() + "/" + range.getTotal());
+
+ // Copy single part range of multipart range.
+ copyStreams(inputStream, to, progressListener, range.getStart() - streamPos, range.getLength(), filename);
+ streamPos = range.getStart() + range.getLength();
}
// End with final multipart boundary.
@@ -248,17 +249,16 @@ public final class FileStreamingUtil {
return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT).build();
}
- private static ResponseEntity handleStandardRangeRequest(final DbArtifact artifact,
- final String filename, final HttpServletResponse response,
- final FileStreamingProgressListener progressListener, final List ranges) {
- final ByteRange r = ranges.get(0);
- response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + r.getStart() + "-" + r.getEnd() + "/" + r.getTotal());
- response.setContentLengthLong(r.getLength());
+ private static ResponseEntity handleStandardRangeRequest(
+ final InputStream inputStream, final String filename, final HttpServletResponse response,
+ final FileStreamingProgressListener progressListener, final ByteRange range) {
+ response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + range.getStart() + "-" + range.getEnd() + "/" + range.getTotal());
+ response.setContentLengthLong(range.getLength());
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
- try (final InputStream from = artifact.getFileInputStream()) {
+ try {
final ServletOutputStream to = response.getOutputStream();
- copyStreams(from, to, progressListener, r.getStart(), r.getLength(), filename);
+ copyStreams(inputStream, to, progressListener, range.getStart(), range.getLength(), filename);
} catch (final IOException e) {
log.error("standardRangeRequest of file ({}) failed!", filename, e);
throw new FileStreamingFailedException(filename);
@@ -267,10 +267,11 @@ public final class FileStreamingUtil {
return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT).build();
}
- private static long copyStreams(final InputStream from, final OutputStream to,
- final FileStreamingProgressListener progressListener, final long start, final long length,
+ private static long copyStreams(
+ final InputStream from, final OutputStream to,
+ final FileStreamingProgressListener progressListener,
+ final long start, final long length,
final String filename) throws IOException {
-
final long startMillis = System.currentTimeMillis();
log.trace("Start of copy-streams of file {} from {} to {}", filename, start, length);
@@ -287,7 +288,7 @@ public final class FileStreamingUtil {
long shippedSinceLastEvent = 0;
while (toContinue) {
- final int r = from.read(buf);
+ final int r = from.read(buf, 0, Math.min(BUFFER_SIZE, toRead > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) toRead));
if (r == -1) {
break;
}
@@ -319,8 +320,8 @@ public final class FileStreamingUtil {
final long totalTime = System.currentTimeMillis() - startMillis;
if (total < length) {
- throw new FileStreamingFailedException(filename + ": " + (length - total)
- + " bytes could not be written to client, total time on write: !" + totalTime + " ms");
+ throw new FileStreamingFailedException(
+ filename + ": " + (length - total) + " bytes could not be written to client, total time on write: !" + totalTime + " ms");
}
log.trace("Finished copy-stream of file {} with length {} in {} ms", filename, length, totalTime);
@@ -417,6 +418,5 @@ public final class FileStreamingUtil {
private long getTotal() {
return total;
}
-
}
-}
+}
\ No newline at end of file
diff --git a/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/util/FileStreamingUtilTest.java b/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/util/FileStreamingUtilTest.java
index d862804ea..c0630efe3 100644
--- a/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/util/FileStreamingUtilTest.java
+++ b/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/util/FileStreamingUtilTest.java
@@ -20,13 +20,13 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
+import java.util.function.Supplier;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
-import org.eclipse.hawkbit.repository.artifact.model.DbArtifact;
-import org.eclipse.hawkbit.repository.artifact.model.DbArtifactHash;
+import org.eclipse.hawkbit.artifact.model.ArtifactStream;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
@@ -43,33 +43,8 @@ class FileStreamingUtilTest {
private static final String CONTENT = "This is some very long string which is intended to test";
private static final byte[] CONTENT_BYTES = CONTENT.getBytes(StandardCharsets.UTF_8);
- private static final DbArtifact TEST_ARTIFACT = new DbArtifact() {
-
- @Override
- public String getArtifactId() {
- return "1";
- }
-
- @Override
- public DbArtifactHash getHashes() {
- return new DbArtifactHash("sha1-111", "md5-123", "sha256-123");
- }
-
- @Override
- public long getSize() {
- return CONTENT_BYTES.length;
- }
-
- @Override
- public String getContentType() {
- return "text/plain";
- }
-
- @Override
- public InputStream getFileInputStream() {
- return new ByteArrayInputStream(CONTENT_BYTES);
- }
- };
+ private static final Supplier TEST_ARTIFACT =
+ () -> new ArtifactStream(new ByteArrayInputStream(CONTENT_BYTES), CONTENT_BYTES.length, "sha1-111");
@Test
void shouldProcessRangeHeaderForMultipartRequests() throws IOException {
@@ -78,11 +53,11 @@ class FileStreamingUtilTest {
Mockito.when(servletResponse.getOutputStream()).thenReturn(outputStream);
final HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class);
- Mockito.when(servletRequest.getHeader("Range")).thenReturn("bytes=0-10,9-15,16-");
+ Mockito.when(servletRequest.getHeader("Range")).thenReturn("bytes=0-10,11-15,16-");
long lastModified = System.currentTimeMillis();
- final ResponseEntity responseEntity = FileStreamingUtil.writeFileResponse(TEST_ARTIFACT,
- "test.file", lastModified, servletResponse, servletRequest, null);
+ final ResponseEntity responseEntity = FileStreamingUtil.writeFileResponse(
+ TEST_ARTIFACT.get(), "test.file", lastModified, servletResponse, servletRequest, null);
assertThat(responseEntity).isNotNull();
verify(servletResponse).setDateHeader(HttpHeaders.LAST_MODIFIED, lastModified);
@@ -92,7 +67,7 @@ class FileStreamingUtilTest {
verify(outputStream).print(stringCaptor.capture());
assertThat(stringCaptor.getValue()).contains("--THIS_STRING_SEPARATES_MULTIPART--");
verify(outputStream, times(3)).write(any(), anyInt(), lenCaptor.capture());
- assertThat(lenCaptor.getAllValues()).containsExactly(11, 7, 39); // Range lengths
+ assertThat(lenCaptor.getAllValues()).containsExactly(11, 5, 39); // Range lengths
}
@Test
@@ -105,8 +80,8 @@ class FileStreamingUtilTest {
final HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class);
Mockito.when(servletRequest.getHeader("Range")).thenReturn("bytes=0-10***,9-15,16-");
- final ResponseEntity responseEntity = FileStreamingUtil.writeFileResponse(TEST_ARTIFACT,
- "test.file", lastModified, servletResponse, servletRequest, null);
+ final ResponseEntity responseEntity = FileStreamingUtil.writeFileResponse(
+ TEST_ARTIFACT.get(), "test.file", lastModified, servletResponse, servletRequest, null);
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
verify(outputStream, times(0)).print(anyString());
diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/InMemoryUserAuthoritiesResolver.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/InMemoryUserAuthoritiesResolver.java
index 540cd897b..eb21431a4 100644
--- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/InMemoryUserAuthoritiesResolver.java
+++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/InMemoryUserAuthoritiesResolver.java
@@ -17,8 +17,7 @@ import java.util.Map;
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
/**
- * An implementation of the {@link UserAuthoritiesResolver} that is based on
- * in-memory user permissions.
+ * An implementation of the {@link UserAuthoritiesResolver} that is based on in-memory user permissions.
*/
public class InMemoryUserAuthoritiesResolver implements UserAuthoritiesResolver {
diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/MdcHandler.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/MdcHandler.java
index d0b2065f4..2daced2d9 100644
--- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/MdcHandler.java
+++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/MdcHandler.java
@@ -77,7 +77,7 @@ public class MdcHandler {
final String tenant;
if (authentication.getDetails() instanceof TenantAwareAuthenticationDetails tenantAwareAuthenticationDetails) {
- tenant = tenantAwareAuthenticationDetails.getTenant();
+ tenant = tenantAwareAuthenticationDetails.tenant();
} else {
tenant = null;
}
diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SpringSecurityAuditorAware.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SpringSecurityAuditorAware.java
index 94d05a824..ce5a07d31 100644
--- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SpringSecurityAuditorAware.java
+++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SpringSecurityAuditorAware.java
@@ -59,7 +59,7 @@ public class SpringSecurityAuditorAware implements AuditorAware {
}
protected String getCurrentAuditor(final Authentication authentication) {
- if (authentication.getDetails() instanceof TenantAwareAuthenticationDetails tenantAwareDetails && tenantAwareDetails.isController()) {
+ if (authentication.getDetails() instanceof TenantAwareAuthenticationDetails tenantAwareDetails && tenantAwareDetails.controller()) {
return "CONTROLLER_PLUG_AND_PLAY";
}
if (authentication.getPrincipal() instanceof UserDetails userDetails) {
diff --git a/hawkbit-test-report/pom.xml b/hawkbit-test-report/pom.xml
index ad03c9956..59d908dfc 100644
--- a/hawkbit-test-report/pom.xml
+++ b/hawkbit-test-report/pom.xml
@@ -35,7 +35,7 @@
org.eclipse.hawkbit
- hawkbit-artifact-repository-filesystem
+ hawkbit-artifact-fs${project.version}
diff --git a/site/content/apis/dmf_api.md b/site/content/apis/dmf_api.md
index 17d0b8b79..27eb6221c 100644
--- a/site/content/apis/dmf_api.md
+++ b/site/content/apis/dmf_api.md
@@ -376,8 +376,8 @@ Example header and payload:
"HTTPS":"https://download-from-url.com"
},
"hashes":{
- "md5":"md5hash",
- "sha1":"sha1hash"
+ "md5":"",
+ "sha1":""
},
"size":512
}],
@@ -508,8 +508,8 @@ Example header and payload:
"HTTPS":"https://download-from-url.com"
},
"hashes":{
- "md5":"md5hash",
- "sha1":"sha1hash"
+ "md5":"",
+ "sha1":""
},
"size":512
}],
@@ -541,8 +541,8 @@ Example header and payload:
"HTTPS":"https://download-from-url.com"
},
"hashes":{
- "md5":"md5hash",
- "sha1":"sha1hash"
+ "md5":"",
+ "sha1":""
},
"size":512
}],
diff --git a/site/content/guides/clustering.md b/site/content/guides/clustering.md
index 3d9c6f062..11c00446b 100644
--- a/site/content/guides/clustering.md
+++ b/site/content/guides/clustering.md
@@ -80,7 +80,7 @@ Add to your `pom.xml` :
Every node is maintaining its own caches independent from other nodes. So there is no globally shared/synchronized cache
instance within the cluster. In order to keep nodes in sync a TTL (time to live) can be set for all caches to ensure
that after some time the cache is refreshed from the database. To enable the TTL just set the property "
-hawkbit.cache.global.ttl" (value in milliseconds). Of course you can implement a shared cache, e.g. Redis.
+hawkbit.cache.ttl" (value in milliseconds). Of course you can implement a shared cache, e.g. Redis.
See [CacheAutoConfiguration](https://github.com/eclipse-hawkbit/hawkbit/blob/master/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/cache/CacheAutoConfiguration.java)
## Schedulers