From 873308fd46d64011c3c52f1a3366d02fe5036d8b Mon Sep 17 00:00:00 2001 From: Gerhard Sonnenberg <53293048+geso02@users.noreply.github.com> Date: Fri, 30 Jun 2023 08:22:32 +0200 Subject: [PATCH] Add submodel registry implementation (#39) * Reference new JsonPatch Based maven plugin this plugin is a simpler approach to extend yaml or json files using jsonpatch * Add extension search and combinedWith aggregation of queries if extensionName is set in a search query, the path must point to the extension value - the extensionName is compared in addition using an equals test - we use combinedWith in the search query to aggregate queries - for all paths outside of a sm all queries must match - for paths pointing to a submodelDescriptor property, there needs to be at least one sm that matches all these queries in order that the hole request matches for that shell * Remove return value of path visitor The return value is not needed in most cases and not reliable considered * Add extension indeces Speeds up searching by extension name and value * Define source folders in POM in some projects we use src/generated/lava or src/main/lombok as source folders we define them now not in the .classpath but also in POM * Add client documentation for aggregate and extension search * Use fixed version for json patch * Remove unused empty file * Add submodel registry implementation * Add submodel registry to parent aggregator * Add and readme and fix readme files * Enable kafak logs and increase connection timeout for tests --- basyx.aasregistry/README.md | 10 +- .../basyx.aasregistry-plugins/README.md | 26 +- basyx.submodelregistry/README.md | 63 ++ .../README.md | 11 + .../open-api/.gitkeep | 0 .../pom.xml | 130 +++ .../templates/libraries/native/api.mustache | 652 ++++++++++++ .../libraries/native/licenseInfo.mustache | 24 + .../.gitignore | 1 + .../README.md | 6 + .../open-api/.gitkeep | 0 .../pom.xml | 118 +++ .../README.md | 14 + .../pom.xml | 78 ++ .../service/tests/BaseInterfaceTest.java | 117 +++ .../service/tests/ExtensionsTest.java | 51 + .../tests/SubmodelRegistryStorageTest.java | 206 ++++ .../service/tests/TestResourcesLoader.java | 123 +++ .../tests/integration/BaseEventListener.java | 96 ++ .../integration/BaseIntegrationTest.java | 337 +++++++ .../src/main/resources/logback-test.xml | 13 + .../service/tests/api/default_repository.json | 8 + .../whenGetSubmodelDescriptorById_thenOk.json | 3 + ...ostSubmodelDescriptorById_thenCreated.json | 3 + ...henPostSubmodelDescriptor_thenApplied.json | 11 + ...nPutSubmodelDescriptor_thenOverridden.json | 8 + .../service/tests/default_repository.json | 30 + .../tests/integration/default_repository.json | 117 +++ ...hBySubmodelDescriptorId_thenGotResult.json | 38 + ...bmodelDescriptorShortId_thenGotResult.json | 38 + ...ubmodelIsCreatedAndDeleted_toregister.json | 18 + ...ngByIdNoSortOrder_thenReturnSortedAsc.json | 140 +++ ...rtingByIdShortAsc_thenReturnSortedAsc.json | 140 +++ ...ingByIdShortDesc_thenReturnSortedDesc.json | 140 +++ .../whenUsePagination_thenUseRefetching.json | 153 +++ ...iptorsAndEmptyRepo_thenEmptyList_repo.json | 2 + ...woPages_thenReturnPageStepByStep_repo.json | 58 ++ ...whenGetAllSubmodelDescriptors_thenAll.json | 30 + ...scritorByIdAndAvailable_thenGotResult.json | 14 + ...lreadyPresent_thenElementIsOverridden.json | 25 + ...resent_thenElementIsOverridden_events.json | 15 + ...sNotAlreadyPresent_thenElementIsAdded.json | 33 + ...eadyPresent_thenElementIsAdded_events.json | 9 + ...RegisterSubmodelDescriptor_thenStored.json | 33 + ...rSubmodelDescriptor_thenStored_events.json | 9 + ..._AvailableUnderNewIdAndTwoEventsFired.json | 30 + ...bleUnderNewIdAndTwoEventsFired_events.json | 24 + ...torById_thenReturnTrueAndEntryRemoved.json | 16 + ..._thenReturnTrueAndEntryRemoved_events.json | 6 + .../Readme.md | 20 + .../pom.xml | 37 + .../InMemorySubmodelStorageConfiguration.java | 44 + .../InMemorySubmodelRegistryStorage.java | 102 ++ .../storage/memory/PaginationSupport.java | 79 ++ .../storage/memory/ThreadSafeAccess.java | 93 ++ ...dSafeSubmodelRegistryStorageDecorator.java | 75 ++ .../resources/application-inMemoryStorage.yml | 3 + .../InMemorySubmodelRegistyStorageTest.java | 44 + .../api/BasyxRegistryApiDelegateTest.java | 177 ++++ .../src/test/resources/logback-test.xml | 13 + .../templates/api.mustache | 186 ++++ .../templates/pojo.mustache | 151 +++ .../Readme.md | 29 + .../pom.xml | 55 + .../KafkaRegistryEventsConfiguration.java | 42 + .../events/kafka/KafkaRegistryEventSink.java | 52 + .../service/events/kafka/LogConsumer.java | 46 + .../resources/application-kafkaEvents.yml | 6 + .../Readme.md | 29 + .../open-api/patch-mongodb-annotations.yaml | 12 + .../pom.xml | 231 +++++ .../configuration/MongoDbConfiguration.java | 52 + .../MongoDbSubmodelRegistryStorage.java | 158 +++ .../resources/application-mongoDbStorage.yml | 6 + .../MongoDbSubmodelRegistryStorageTest.java | 74 ++ .../src/test/resources/logback-test.xml | 13 + .../testAdministrationCreatorKeysType.json | 7 + .../tests/testAndQuerySubmodelFunctional.json | 3 + .../service/tests/testAssetKindEnum.json | 3 + ...colInformationEndpointProtocolVersion.json | 11 + ...formationEndpointProtocolVersionRegex.json | 16 + .../aasregistry/service/tests/testId.json | 3 + .../service/tests/testIdShort.json | 3 + ...testMatchSubmodelFunctionalProjection.json | 31 + .../testMatchSubmodelListProjection.json | 50 + .../tests/testMultipleShellExtensions.json | 32 + ...testRegexSubmodelFunctionalProjection.json | 31 + .../testRegexSubmodelListProjection.json | 50 + .../service/tests/testShellExtensionName.json | 14 + ...colInformationSecurityAttributesValue.json | 15 + .../service/tests/testSubmodelId.json | 7 + .../service/tests/testSubmodelIdShort.json | 7 + .../Readme.md | 9 + .../pom.xml | 66 ++ .../src/main/docker/Dockerfile | 16 + .../src/main/resources/.empty | 1 + ...aEventsInMemoryStorageIntegrationTest.java | 70 ++ .../src/test/resources/logback-test.xml | 13 + .../Readme.md | 9 + .../pom.xml | 65 ++ .../src/main/docker/Dockerfile | 16 + .../src/main/resources/.empty | 1 + ...kaEventsMongoDbStorageIntegrationTest.java | 88 ++ .../src/test/resources/logback-test.xml | 13 + .../Readme.md | 9 + .../pom.xml | 55 + .../src/main/docker/Dockerfile | 16 + .../src/main/resources/.empty | 1 + .../Readme.md | 9 + .../pom.xml | 47 + .../src/main/docker/Dockerfile | 16 + .../src/main/resources/.empty | 1 + .../basyx.submodelregistry-service/README.md | 7 + .../open-api/.gitkeep | 0 .../basyx.submodelregistry-service/pom.xml | 211 ++++ .../api/BasyxDescriptionApiDelegate.java | 76 ++ .../api/BasyxSubmodelRegistryApiDelegate.java | 110 ++ .../configuration/RestConfiguration.java | 50 + .../service/errors/BasyxControllerAdvice.java | 79 ++ .../DescriptorAlreadyExistsException.java | 37 + .../errors/DescriptorNotFoundException.java | 37 + .../SubmodelAlreadyExistsException.java | 37 + .../errors/SubmodelNotFoundException.java | 37 + .../service/events/RegistryEvent.java | 49 + .../service/events/RegistryEventLogSink.java | 57 ++ .../service/events/RegistryEventSink.java | 31 + .../CursorEncodingRegistryStorage.java | 70 ++ .../service/storage/CursorResult.java | 42 + .../service/storage/PaginationInfo.java | 49 + ...onEventSendingSubmodelRegistryStorage.java | 85 ++ .../storage/SubmodelRegistryStorage.java | 47 + .../main/resources/application-logEvents.yml | 3 + .../src/main/resources/application.yml | 48 + .../tests/DescriptionProfilesTest.java | 93 ++ .../templates/api.mustache | 270 +++++ .../templates/apiDelegate.mustache | 73 ++ .../GenerateByteUrlEncodedBase64Id.java | 26 + .../docker-compose/Readme.md | 13 + .../docker-compose/build-images.sh | 11 + .../docker-compose/docker-compose-down.sh | 10 + .../docker-compose/docker-compose-up.sh | 21 + .../docker-compose/docker-compose.yml | 163 +++ ...ation-V3.0.1_SSP-001-resolved-altered.yaml | 952 ++++++++++++++++++ ...Specification-V3.0.1_SSP-001-resolved.yaml | 944 +++++++++++++++++ .../open-api/patch-base-extensions.yaml | 54 + basyx.submodelregistry/pom.xml | 343 +++++++ pom.xml | 1 + 147 files changed, 9966 insertions(+), 30 deletions(-) create mode 100644 basyx.submodelregistry/README.md create mode 100644 basyx.submodelregistry/basyx.submodelregistry-client-native/README.md create mode 100644 basyx.submodelregistry/basyx.submodelregistry-client-native/open-api/.gitkeep create mode 100644 basyx.submodelregistry/basyx.submodelregistry-client-native/pom.xml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-client-native/templates/libraries/native/api.mustache create mode 100644 basyx.submodelregistry/basyx.submodelregistry-client-native/templates/libraries/native/licenseInfo.mustache create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basemodel/.gitignore create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basemodel/README.md create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basemodel/open-api/.gitkeep create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basemodel/pom.xml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/README.md create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/pom.xml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/BaseInterfaceTest.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/ExtensionsTest.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/SubmodelRegistryStorageTest.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/TestResourcesLoader.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/BaseEventListener.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/BaseIntegrationTest.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/logback-test.xml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/default_repository.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/whenGetSubmodelDescriptorById_thenOk.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/whenPostSubmodelDescriptorById_thenCreated.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/whenPostSubmodelDescriptor_thenApplied.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/whenPutSubmodelDescriptor_thenOverridden.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/default_repository.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/default_repository.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenMatchSearchBySubmodelDescriptorId_thenGotResult.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenRegexSearchBySubmodelDescriptorShortId_thenGotResult.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenRegisterAndUnregisterSubmodel_thenSubmodelIsCreatedAndDeleted_toregister.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenSearchWithSortingByIdNoSortOrder_thenReturnSortedAsc.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenSearchWithSortingByIdShortAsc_thenReturnSortedAsc.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenSearchWithSortingByIdShortDesc_thenReturnSortedDesc.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenUsePagination_thenUseRefetching.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenGetAllSubmodelDescriptorsAndEmptyRepo_thenEmptyList_repo.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenGetAllSubmodelDescriptorsOverTwoPages_thenReturnPageStepByStep_repo.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenGetAllSubmodelDescriptors_thenAll.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenGetSubmodelDescritorByIdAndAvailable_thenGotResult.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptorAndWasAlreadyPresent_thenElementIsOverridden.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptorAndWasAlreadyPresent_thenElementIsOverridden_events.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptorAndWasNotAlreadyPresent_thenElementIsAdded.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptorAndWasNotAlreadyPresent_thenElementIsAdded_events.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptor_thenStored.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptor_thenStored_events.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegistrationUpdateForNewId_AvailableUnderNewIdAndTwoEventsFired.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegistrationUpdateForNewId_AvailableUnderNewIdAndTwoEventsFired_events.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenUnregisterSubmodelDescriptorById_thenReturnTrueAndEntryRemoved.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenUnregisterSubmodelDescriptorById_thenReturnTrueAndEntryRemoved_events.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/Readme.md create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/pom.xml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/configuration/InMemorySubmodelStorageConfiguration.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/InMemorySubmodelRegistryStorage.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/PaginationSupport.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/ThreadSafeAccess.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/ThreadSafeSubmodelRegistryStorageDecorator.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/resources/application-inMemoryStorage.yml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/InMemorySubmodelRegistyStorageTest.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/BasyxRegistryApiDelegateTest.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/test/resources/logback-test.xml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/templates/api.mustache create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/templates/pojo.mustache create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/Readme.md create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/pom.xml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/KafkaRegistryEventsConfiguration.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/events/kafka/KafkaRegistryEventSink.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/events/kafka/LogConsumer.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/src/main/resources/application-kafkaEvents.yml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/Readme.md create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/open-api/patch-mongodb-annotations.yaml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/pom.xml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/configuration/MongoDbConfiguration.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/mongodb/MongoDbSubmodelRegistryStorage.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/main/resources/application-mongoDbStorage.yml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/MongoDbSubmodelRegistryStorageTest.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/logback-test.xml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testAdministrationCreatorKeysType.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testAndQuerySubmodelFunctional.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testAssetKindEnum.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testEndpointsProtocolInformationEndpointProtocolVersion.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testEndpointsProtocolInformationEndpointProtocolVersionRegex.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testId.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testIdShort.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testMatchSubmodelFunctionalProjection.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testMatchSubmodelListProjection.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testMultipleShellExtensions.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testRegexSubmodelFunctionalProjection.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testRegexSubmodelListProjection.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testShellExtensionName.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testSmEndpointsProtocolInformationSecurityAttributesValue.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testSubmodelId.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testSubmodelIdShort.json create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/Readme.md create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/pom.xml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/src/main/docker/Dockerfile create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/src/main/resources/.empty create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/KafkaEventsInMemoryStorageIntegrationTest.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/src/test/resources/logback-test.xml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/Readme.md create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/pom.xml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/src/main/docker/Dockerfile create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/src/main/resources/.empty create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/mongodb/KafkaEventsMongoDbStorageIntegrationTest.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/src/test/resources/logback-test.xml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/Readme.md create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/pom.xml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/src/main/docker/Dockerfile create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/src/main/resources/.empty create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/Readme.md create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/pom.xml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/src/main/docker/Dockerfile create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/src/main/resources/.empty create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/README.md create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/open-api/.gitkeep create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/pom.xml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/api/BasyxDescriptionApiDelegate.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/api/BasyxSubmodelRegistryApiDelegate.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/configuration/RestConfiguration.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/errors/BasyxControllerAdvice.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/errors/DescriptorAlreadyExistsException.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/errors/DescriptorNotFoundException.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/errors/SubmodelAlreadyExistsException.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/errors/SubmodelNotFoundException.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/events/RegistryEvent.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/events/RegistryEventLogSink.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/events/RegistryEventSink.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/CursorEncodingRegistryStorage.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/CursorResult.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/PaginationInfo.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/RegistrationEventSendingSubmodelRegistryStorage.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/SubmodelRegistryStorage.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/src/main/resources/application-logEvents.yml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/src/main/resources/application.yml create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/DescriptionProfilesTest.java create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/templates/api.mustache create mode 100644 basyx.submodelregistry/basyx.submodelregistry-service/templates/apiDelegate.mustache create mode 100644 basyx.submodelregistry/docker-compose/GenerateByteUrlEncodedBase64Id.java create mode 100644 basyx.submodelregistry/docker-compose/Readme.md create mode 100644 basyx.submodelregistry/docker-compose/build-images.sh create mode 100644 basyx.submodelregistry/docker-compose/docker-compose-down.sh create mode 100644 basyx.submodelregistry/docker-compose/docker-compose-up.sh create mode 100644 basyx.submodelregistry/docker-compose/docker-compose.yml create mode 100644 basyx.submodelregistry/open-api/Plattform_i40-SubmodelRegistryServiceSpecification-V3.0.1_SSP-001-resolved-altered.yaml create mode 100644 basyx.submodelregistry/open-api/Plattform_i40-SubmodelRegistryServiceSpecification-V3.0.1_SSP-001-resolved.yaml create mode 100644 basyx.submodelregistry/open-api/patch-base-extensions.yaml create mode 100644 basyx.submodelregistry/pom.xml diff --git a/basyx.aasregistry/README.md b/basyx.aasregistry/README.md index eb2c73e00..f90df3b50 100644 --- a/basyx.aasregistry/README.md +++ b/basyx.aasregistry/README.md @@ -12,11 +12,11 @@ This is a Java-based implementation of the Asset Administration Shell Registry s [basyx.aasregistry-service-basemodel](basyx.aasregistry-service-basemodel/README.md) provides a base model implementation that should be used if you do not need specific model annotations for your storage. It is used for the in-memory storage implementation and you need to add it explicitly as dependency for your server deployment as it is defined as 'provided' dependency in the [basyx.aasregistry-service](basyx.aasregistry-service/README.md) POM. -[basyx.aasregistry-service-base-tests](basyx.aasregistry-service-base-tests/README.md) provides helper classes and abstract test classes that can be extended in storage tests or integration tests. The abstract test classes already define test methods so that you will get a good test coverage without writing any additional test cases. +[basyx.aasregistry-service-basetests](basyx.aasregistry-service-basetests/README.md) provides helper classes and abstract test classes that can be extended in storage tests or integration tests. The abstract test classes already define test methods so that you will get a good test coverage without writing any additional test cases. -[basyx.aasregistry-service-mongodb-storage](basyx.aasregistry-service-mongodb-storage/README.md) provides a registry-storage implementation based on mongoDB that could be used as storage for [aasregistry-service](aasregistry-service/README.md). It comes with java-based model classes, annotated with mongoDB annotations. +[basyx.aasregistry-service-mongodb-storage](basyx.aasregistry-service-mongodb-storage/README.md) provides a registry-storage implementation based on mongoDB that could be used as storage for [aasregistry-service](basyx.aasregistry-service/README.md). It comes with java-based model classes, annotated with mongoDB annotations. -[basyx.aasregistry-service-inmemory-storage](basyx.aasregistry-service-inmemory-storage/README.md) provides a non-persistent registry-storage implementation where instances are stored in hash maps. It can be used as storage for [aasregistry-service](aasregistry-service/README.md). +[basyx.aasregistry-service-inmemory-storage](basyx.aasregistry-service-inmemory-storage/README.md) provides a non-persistent registry-storage implementation where instances are stored in hash maps. It can be used as storage for [aasregistry-service](basyx.aasregistry-service/README.md). [basyx.aasregistry-service-kafka-events](basyx.aasregistry-service-kafka-events/README.md) extends basyx.aasregistry-service with a registry-event-sink implementation that delivers shell descriptor and submodel registration events using Apache Kafka. The default provided by aasregistry-service just logs the events. @@ -33,7 +33,7 @@ A docker-compose file that illustrates the setup can be found in the [docker-com # Important -The REST API and the client implementation will not be modified until a new major version is released or an update of the openAPI definition. All server-side classes and the plugins are not intended to be used as programming library. They could be updated or removed then a new minor version is released. +The REST API and the client implementation will not be modified - if not a SNAPSHOT version - until a new major version is released or an update of the openAPI definition. All server-side classes and the plugins are not intended to be used as programming library. They could be updated or removed then a new minor version is released. # Build Resources @@ -60,7 +60,7 @@ Or you can directly push them from maven. ``` shell MAVEN_OPS='-Xmx2048 -Xms1024' mvn deploy -Ddocker.registry=docker.io -Ddocker.username=eclipsebasyx -Ddocker.password=pwd ``` -In addition maven deploy will also deploy your maven artifacts, so you can do everything in one step. +In addition, maven deploy will also deploy your maven artifacts, so you can do everything in one step. Have a look at the *docker-compose* sub-folder to see how the created images could be referenced in docker-compose files. diff --git a/basyx.aasregistry/basyx.aasregistry-plugins/README.md b/basyx.aasregistry/basyx.aasregistry-plugins/README.md index fb6d7de22..b70bd5f42 100644 --- a/basyx.aasregistry/basyx.aasregistry-plugins/README.md +++ b/basyx.aasregistry/basyx.aasregistry-plugins/README.md @@ -1,30 +1,6 @@ # Basyx AAS Registry Plugins -This project provides two maven plugins. - -Using the first plugin, you can perform an overlay operation based on two YAML files. This could be quite useful when you want to extend existing openAPI definitions or want to enhance a definition with annotations that should be processed by an openAPI-generator. - -To use the plugin embed this snippet into your POM file and specify an appropriate version: - -``` xml - - org.eclipse.digitaltwin.basyx.aasregistry.service - basyx.aasregistry-plugins - - - - yaml-overlay - - - - - ${project.basedir}/base.yaml - ${project.basedir}/overlay.yaml - ${project.basedir}/result.yaml - - -``` -The other maven plugin can be used to generate builder classes that create search paths for the registry-service-based POJO classes. The plugin traverses the referenced class and its fields and generates a class that can be used to set up these search paths. +This project provides a maven plugin that can be used to generate builder classes that create search paths for the registry-service-based POJO classes. The plugin traverses the referenced class and its fields and generates a class that can be used to set up these search paths. As we use the same search path in our AAS registry client, this generator can also be used there. The main benefit is that we will avoid typos when using the generated client and do not need to specify the string directly. diff --git a/basyx.submodelregistry/README.md b/basyx.submodelregistry/README.md new file mode 100644 index 000000000..8f7a32f30 --- /dev/null +++ b/basyx.submodelregistry/README.md @@ -0,0 +1,63 @@ +# Basyx Submodel Registry + +This is a Java-based implementation of the Submodel Registry server and client based on the corresponding [Open-API specification](ttps://app.swaggerhub.com/apis/Plattform_i40/SubmodelRegistryServiceSpecification/V3.0.1_SSP-001) of the German Plattform Industrie 4.0. + +[basyx.submodelregistry-client-native](basyx.submodelregistry-client-native/README.md) can be used to interact with the backend to register or unregister descriptors and submodels or perform search operations. + +[basyx.submodelregistry-service](basyx.submodelregistry-service/README.md) provides the application server to access the submodel descriptor storage and offers an API for REST-based communication. + +[basyx.submodelregistry-service-basemodel](basyx.submodelregistry-service-basemodel/README.md) provides a base model implementation that should be used if you do not need specific model annotations for your storage. It is used for the in-memory storage implementation and you need to add it explicitly as dependency for your server deployment as it is defined as 'provided' dependency in the [basyx.submodelregistry-service](basyx.submodelregistry-service/README.md) POM. + +[basyx.submodelregistry-service-basetests](basyx.submodelregistry-service-basetests/README.md) provides helper classes and abstract test classes that can be extended in storage tests or integration tests. The abstract test classes already define test methods so that you will get a good test coverage without writing any additional test cases. + +[basyx.submodelregistry-service-mongodb-storage](basyx.submodelregistry-service-mongodb-storage/README.md) provides a registry-storage implementation based on mongoDB that could be used as storage for [submodelregistry-service](basyx.submodelregistry-service/README.md). It comes with java-based model classes, annotated with mongoDB annotations. + +[basyx.submodelregistry-service-inmemory-storage](basyx.submodelregistry-service-inmemory-storage/README.md) provides a non-persistent registry-storage implementation where instances are stored in hash maps. It can be used as storage for [submodelregistry-service](basyx.submodelregistry-service/README.md). + +[basyx.submodelregistry-service-kafka-events](basyx.submodelregistry-service-kafka-events/README.md) extends basyx.submodelregistry-service with a registry-event-sink implementation that delivers shell descriptor and submodel registration events using Apache Kafka. The default provided by submodelregistry-service just logs the events. + +[basyx.submodelregistry-service-release-kafka-mongodb](basyx.submodelregistry-service-release-kafka-mongodb/README.md) is used to combine the server artifacts to a release image that uses [Apache Kafka](https://kafka.apache.org/) as event sink and [MongoDB](https://www.mongodb.com/) as storage. + +[basyx.submodelregistry-service-release-kafka-mem](basyx.submodelregistry-service-release-kafka-mem/README.md) is used to combine the server artifacts to a release image that uses Apache Kafka as event sink and an in-memory storage. + +[basyx.submodelregistry-service-release-log-mongodb](basyx.submodelregistry-service-release-log-mongodb/README.md) is used to combine the server artifacts to a release image that logs registry events and uses MongoDB as data storage. + +[basyx.submodelregistry-service-release-log-mem](basyx.submodelregistry-service-release-log-mem/README.md) is used to combine the server artifacts to a release image that logs registry events and an in-memory storage. + +A docker-compose file that illustrates the setup can be found in the [docker-compose](docker-compose/docker-compose.yml) folder. + + +# Important + +The REST API and the client implementation will not be modified - if not a SNAPSHOT version - until a new major version is released or an update of the openAPI definition. All server-side classes and the plugins are not intended to be used as programming library. They could be updated or removed then a new minor version is released. + +# Build Resources + +To build the images run these commands from this folder or for the parent project pom: + +Install maven generate jars: + +``` shell +mvn clean install +``` + +In order to build the docker images, you need to specify *docker.username* and *docker.password* properties (here without running tests): + +``` shell +MAVEN_OPS='-Xmx2048 -Xms1024' mvn clean install -DskipTests -Ddocker.username=eclipsebasyx -Ddocker.password="" +``` + +You can now check your images from command-line and push the images: +``` shell +docker images ... +``` +Or you can directly push them from maven. + +``` shell +MAVEN_OPS='-Xmx2048 -Xms1024' mvn deploy -Ddocker.registry=docker.io -Ddocker.username=eclipsebasyx -Ddocker.password=pwd +``` +In addition, maven deploy will also deploy your maven artifacts, so you can do everything in one step. + +Have a look at the *docker-compose* sub-folder to see how the created images could be referenced in docker-compose files. + +Consider updating the [image name pattern](pom.xml#L16) if you want a different image name. \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-client-native/README.md b/basyx.submodelregistry/basyx.submodelregistry-client-native/README.md new file mode 100644 index 000000000..f4fb97c1f --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-client-native/README.md @@ -0,0 +1,11 @@ +# Basyx Submodel Registry Client Native + +This is the generated java openAPI client (based on native java with jackson parsing) that can be used to communicate with the submodel registry server. + +To use the client in your maven projects define the following dependency: +```xml + + org.eclipse.digitaltwin.basyx.submodelregistry + basyx.submodelregistry-client-native + +``` diff --git a/basyx.submodelregistry/basyx.submodelregistry-client-native/open-api/.gitkeep b/basyx.submodelregistry/basyx.submodelregistry-client-native/open-api/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/basyx.submodelregistry/basyx.submodelregistry-client-native/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-client-native/pom.xml new file mode 100644 index 000000000..bb7a9ecb5 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-client-native/pom.xml @@ -0,0 +1,130 @@ + + 4.0.0 + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry + ${revision} + .. + + + basyx.submodelregistry-client-native + + jar + + ${project.basedir}/${openapi.folder.name} + ${openapi.result.folder}/${openapi.name} + + + ${generated.folder}/java + + + maven-clean-plugin + + + + ${project.basedir}/${generated.folder} + + **/.gitkeep + + false + + + ${openapi.result.folder} + + **/.gitkeep + + false + + + + + + de.dfki.cos.basys.common + jsonpatch-maven-plugin + + + generate-sources + + jsonpatch-maven-plugin + + + ${project.basedir}/../${openapi.folder.name}/${openapi.base.name} + ${project.basedir}/../${openapi.folder.name}/${patch.base-extensions.name} + ${openapi.result.file} + + + + + + org.openapitools + openapi-generator-maven-plugin + + + + generate + + + java + native + ${openapi.result.file} + ${project.basedir}/${generated.folder} + org.eclipse.digitaltwin.basyx.submodelregistry.client.api + org.eclipse.digitaltwin.basyx.submodelregistry.client + org.eclipse.digitaltwin.basyx.submodelregistry.client.model + true + true + false + false + false + true + ${project.basedir}/templates + AbstractOpenApiSchema.java,ServerConfiguration.java,ServerVariable.java,Configuration.java,JSON.java,ApiException.java,ApiResponse.java,ApiClient.java,Pair.java,RFC3339DateFormat.java + + true + java8 + java + + + + + + + + + + + org.apache.maven.plugins + maven-plugin-plugin + 3.9.0 + + + + + + + + org.openapitools + jackson-databind-nullable + + + javax.annotation + javax.annotation-api + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + com.google.code.findbugs + jsr305 + + + + diff --git a/basyx.submodelregistry/basyx.submodelregistry-client-native/templates/libraries/native/api.mustache b/basyx.submodelregistry/basyx.submodelregistry-client-native/templates/libraries/native/api.mustache new file mode 100644 index 000000000..eea74159a --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-client-native/templates/libraries/native/api.mustache @@ -0,0 +1,652 @@ +{{>licenseInfo}} +package {{package}}; + +import {{invokerPackage}}.ApiClient; +import {{invokerPackage}}.ApiException; +import {{invokerPackage}}.ApiResponse; +import {{invokerPackage}}.Pair; + +{{#imports}} +import {{import}}; +{{/imports}} + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +{{#hasFormParamsInSpec}} +import org.apache.http.HttpEntity; +import org.apache.http.NameValuePair; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; + +{{/hasFormParamsInSpec}} +import java.io.InputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.net.http.HttpRequest; +import java.nio.channels.Channels; +import java.nio.channels.Pipe; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; + +{{^fullJavaUtil}} +import java.util.ArrayList; +import java.util.StringJoiner; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +{{/fullJavaUtil}} +{{#asyncNative}} + +import java.util.concurrent.CompletableFuture; +{{/asyncNative}} + +{{>generatedAnnotation}} +{{#operations}} +public class {{classname}} { + private final HttpClient memberVarHttpClient; + private final ObjectMapper memberVarObjectMapper; + private final String memberVarBaseUri; + private final {{#fullJavaUtil}}java.util.function.{{/fullJavaUtil}}Consumer memberVarInterceptor; + private final Duration memberVarReadTimeout; + private final {{#fullJavaUtil}}java.util.function.{{/fullJavaUtil}}Consumer> memberVarResponseInterceptor; + private final {{#fullJavaUtil}}java.util.function.{{/fullJavaUtil}}Consumer> memberVarAsyncResponseInterceptor; + + public {{classname}}() { + this(new ApiClient()); + } + + public {{classname}}(String protocol, String host, int port) { + this(protocol + "://" + host + ":" + port + "/api/v3.0"); + } + + public {{classname}}(String basePath) { + this(withBaseUri(new ApiClient(), basePath)); + } + + private static ApiClient withBaseUri(ApiClient client, String uri) { + client.updateBaseUri(uri); + return client; + } + + public {{classname}}(ApiClient apiClient) { + memberVarHttpClient = apiClient.getHttpClient(); + memberVarObjectMapper = apiClient.getObjectMapper(); + memberVarBaseUri = apiClient.getBaseUri(); + memberVarInterceptor = apiClient.getRequestInterceptor(); + memberVarReadTimeout = apiClient.getReadTimeout(); + memberVarResponseInterceptor = apiClient.getResponseInterceptor(); + memberVarAsyncResponseInterceptor = apiClient.getAsyncResponseInterceptor(); + } + {{#asyncNative}} + + private ApiException getApiException(String operationId, HttpResponse response) { + String message = formatExceptionMessage(operationId, response.statusCode(), response.body()); + return new ApiException(response.statusCode(), message, response.headers(), response.body()); + } + {{/asyncNative}} + {{^asyncNative}} + + protected ApiException getApiException(String operationId, HttpResponse response) throws IOException { + String body = response.body() == null ? null : new String(response.body().readAllBytes()); + String message = formatExceptionMessage(operationId, response.statusCode(), body); + return new ApiException(response.statusCode(), message, response.headers(), body); + } + {{/asyncNative}} + + private String formatExceptionMessage(String operationId, int statusCode, String body) { + if (body == null || body.isEmpty()) { + body = "[no body]"; + } + return operationId + " call failed with: " + statusCode + " - " + body; + } + + {{#operation}} + {{#vendorExtensions.x-group-parameters}} + {{#hasParams}} + /** + * {{summary}} + * {{notes}} + * @param apiRequest {@link API{{operationId}}Request} + {{#returnType}} + * @return {{#asyncNative}}CompletableFuture<{{/asyncNative}}{{returnType}}{{#asyncNative}}>{{/asyncNative}} + {{/returnType}} + {{^returnType}} + {{#asyncNative}} + * @return CompletableFuture<Void> + {{/asyncNative}} + {{/returnType}} + * @throws ApiException if fails to make API call + {{#isDeprecated}} + * @deprecated + {{/isDeprecated}} + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + {{#isDeprecated}} + @Deprecated + {{/isDeprecated}} + public {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}}{{/returnType}}{{^returnType}}{{#asyncNative}}CompletableFuture{{/asyncNative}}{{^asyncNative}}void{{/asyncNative}}{{/returnType}} {{operationId}}(API{{operationId}}Request apiRequest) throws ApiException { + {{#allParams}} + {{{dataType}}} {{paramName}} = apiRequest.{{paramName}}(); + {{/allParams}} + {{#returnType}}return {{/returnType}}{{^returnType}}{{#asyncNative}}return {{/asyncNative}}{{/returnType}}{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + } + + /** + * {{summary}} + * {{notes}} + * @param apiRequest {@link API{{operationId}}Request} + * @return {{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse<{{returnType}}{{^returnType}}Void{{/returnType}}>{{#asyncNative}}>{{/asyncNative}} + * @throws ApiException if fails to make API call + {{#isDeprecated}} + * @deprecated + {{/isDeprecated}} + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + {{#isDeprecated}} + @Deprecated + {{/isDeprecated}} + public {{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}>{{#asyncNative}}>{{/asyncNative}} {{operationId}}WithHttpInfo(API{{operationId}}Request apiRequest) throws ApiException { + {{#allParams}} + {{{dataType}}} {{paramName}} = apiRequest.{{paramName}}(); + {{/allParams}} + return {{operationId}}WithHttpInfo({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + } + + {{/hasParams}} + {{/vendorExtensions.x-group-parameters}} + /** + * {{summary}} + * {{notes}} + {{#allParams}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/isContainer}}{{/required}} + {{/allParams}} + {{#returnType}} + * @return {{#asyncNative}}CompletableFuture<{{/asyncNative}}{{returnType}}{{#asyncNative}}>{{/asyncNative}} + {{/returnType}} + {{^returnType}} + {{#asyncNative}} + * @return CompletableFuture<Void> + {{/asyncNative}} + {{/returnType}} + * @throws ApiException if fails to make API call + {{#isDeprecated}} + * @deprecated + {{/isDeprecated}} + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + {{#isDeprecated}} + @Deprecated + {{/isDeprecated}} + public {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}}{{/returnType}}{{^returnType}}{{#asyncNative}}CompletableFuture{{/asyncNative}}{{^asyncNative}}void{{/asyncNative}}{{/returnType}} {{operationId}}({{#allParams}}{{#vendorExtensions.x-utf8-base64-url-encoded-as-string}}String{{/vendorExtensions.x-utf8-base64-url-encoded-as-string}}{{^vendorExtensions.x-utf8-base64-url-encoded-as-string}}{{{dataType}}}{{/vendorExtensions.x-utf8-base64-url-encoded-as-string}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + {{^asyncNative}} + {{#returnType}}ApiResponse<{{{.}}}> localVarResponse = {{/returnType}}{{operationId}}WithHttpInfo({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + {{#returnType}} + return localVarResponse.getData(); + {{/returnType}} + {{/asyncNative}} + {{#asyncNative}} + try { + HttpRequest.Builder localVarRequestBuilder = {{operationId}}RequestBuilder({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + return memberVarHttpClient.sendAsync( + localVarRequestBuilder.build(), + HttpResponse.BodyHandlers.ofString()).thenComposeAsync(localVarResponse -> { + if (localVarResponse.statusCode()/ 100 != 2) { + return CompletableFuture.failedFuture(getApiException("{{operationId}}", localVarResponse)); + } + {{#returnType}} + try { + String responseBody = localVarResponse.body(); + return CompletableFuture.completedFuture( + responseBody == null || responseBody.isBlank() ? null : memberVarObjectMapper.readValue(responseBody, new TypeReference<{{{returnType}}}>() {}) + ); + } catch (IOException e) { + return CompletableFuture.failedFuture(new ApiException(e)); + } + {{/returnType}} + {{^returnType}} + return CompletableFuture.completedFuture(null); + {{/returnType}} + }); + } + catch (ApiException e) { + return CompletableFuture.failedFuture(e); + } + {{/asyncNative}} + } + + /** + * {{summary}} + * {{notes}} + {{#allParams}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/isContainer}}{{/required}} + {{/allParams}} + * @return {{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse<{{returnType}}{{^returnType}}Void{{/returnType}}>{{#asyncNative}}>{{/asyncNative}} + * @throws ApiException if fails to make API call + {{#isDeprecated}} + * @deprecated + {{/isDeprecated}} + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + {{#isDeprecated}} + @Deprecated + {{/isDeprecated}} + public {{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}>{{#asyncNative}}>{{/asyncNative}} {{operationId}}WithHttpInfo({{#allParams}}{{#vendorExtensions.x-utf8-base64-url-encoded-as-string}}String{{/vendorExtensions.x-utf8-base64-url-encoded-as-string}}{{^vendorExtensions.x-utf8-base64-url-encoded-as-string}}{{{dataType}}}{{/vendorExtensions.x-utf8-base64-url-encoded-as-string}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + {{^asyncNative}} + HttpRequest.Builder localVarRequestBuilder = {{operationId}}RequestBuilder({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + try { + HttpResponse localVarResponse = memberVarHttpClient.send( + localVarRequestBuilder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); + } + try { + if (localVarResponse.statusCode()/ 100 != 2) { + throw getApiException("{{operationId}}", localVarResponse); + } + {{#vendorExtensions.x-java-text-plain-string}} + // for plain text response + if (localVarResponse.headers().map().containsKey("Content-Type") && + "text/plain".equalsIgnoreCase(localVarResponse.headers().map().get("Content-Type").get(0))) { + java.util.Scanner s = new java.util.Scanner(localVarResponse.body()).useDelimiter("\\A"); + String responseBodyText = s.hasNext() ? s.next() : ""; + return new ApiResponse( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + responseBodyText + ); + } else { + throw new RuntimeException("Error! The response Content-Type is supposed to be `text/plain` but it's not: " + localVarResponse); + } + {{/vendorExtensions.x-java-text-plain-string}} + {{^vendorExtensions.x-java-text-plain-string}} + return new ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}>( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + {{#returnType}} + localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference<{{{returnType}}}>() {}) // closes the InputStream + {{/returnType}} + {{^returnType}} + null + {{/returnType}} + ); + {{/vendorExtensions.x-java-text-plain-string}} + } finally { + {{^returnType}} + // Drain the InputStream + while (localVarResponse.body().read() != -1) { + // Ignore + } + localVarResponse.body().close(); + {{/returnType}} + } + } catch (IOException e) { + throw new ApiException(e); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + {{/asyncNative}} + {{#asyncNative}} + try { + HttpRequest.Builder localVarRequestBuilder = {{operationId}}RequestBuilder({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + return memberVarHttpClient.sendAsync( + localVarRequestBuilder.build(), + HttpResponse.BodyHandlers.ofString()).thenComposeAsync(localVarResponse -> { + if (memberVarAsyncResponseInterceptor != null) { + memberVarAsyncResponseInterceptor.accept(localVarResponse); + } + if (localVarResponse.statusCode()/ 100 != 2) { + return CompletableFuture.failedFuture(getApiException("{{operationId}}", localVarResponse)); + } + {{#returnType}} + try { + String responseBody = localVarResponse.body(); + return CompletableFuture.completedFuture( + new ApiResponse<{{{returnType}}}>( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + responseBody == null || responseBody.isBlank() ? null : memberVarObjectMapper.readValue(responseBody, new TypeReference<{{{returnType}}}>() {})) + ); + } catch (IOException e) { + return CompletableFuture.failedFuture(new ApiException(e)); + } + {{/returnType}} + {{^returnType}} + return CompletableFuture.completedFuture( + new ApiResponse(localVarResponse.statusCode(), localVarResponse.headers().map(), null) + ); + {{/returnType}} + } + ); + } + catch (ApiException e) { + return CompletableFuture.failedFuture(e); + } + {{/asyncNative}} + } + + private HttpRequest.Builder {{operationId}}RequestBuilder({{#allParams}}{{#vendorExtensions.x-utf8-base64-url-encoded-as-string}}String{{/vendorExtensions.x-utf8-base64-url-encoded-as-string}}{{^vendorExtensions.x-utf8-base64-url-encoded-as-string}}{{{dataType}}}{{/vendorExtensions.x-utf8-base64-url-encoded-as-string}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + {{#allParams}} + {{#required}} + // verify the required parameter '{{paramName}}' is set + if ({{paramName}} == null) { + throw new ApiException(400, "Missing the required parameter '{{paramName}}' when calling {{operationId}}"); + } + {{/required}} + {{/allParams}} + + {{#hasPathParams}}{{#pathParams}}{{#vendorExtensions.x-utf8-base64-url-encoded-as-string}}String {{{paramName}}}AsBase64EncodedParam = {{{paramName}}} == null ? null : new String(java.util.Base64.getUrlEncoder().encode({{paramName}}.getBytes(java.nio.charset.StandardCharsets.UTF_8)), java.nio.charset.StandardCharsets.UTF_8); + {{/vendorExtensions.x-utf8-base64-url-encoded-as-string}}{{/pathParams}}{{/hasPathParams}} + + HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); + + {{! Switch delimiters for baseName so we can write constants like "{query}" }} + String localVarPath = "{{{path}}}"{{#pathParams}} + .replace({{=<% %>=}}"{<%baseName%>}"<%={{ }}=%>, ApiClient.urlEncode({{{paramName}}}{{#vendorExtensions.x-utf8-base64-url-encoded-as-string}}AsBase64EncodedParam{{/vendorExtensions.x-utf8-base64-url-encoded-as-string}}.toString())){{/pathParams}}; + + {{#hasQueryParams}} + {{javaUtilPrefix}}List localVarQueryParams = new {{javaUtilPrefix}}ArrayList<>(); + {{javaUtilPrefix}}StringJoiner localVarQueryStringJoiner = new {{javaUtilPrefix}}StringJoiner("&"); + String localVarQueryParameterBaseName; + {{#queryParams}} + localVarQueryParameterBaseName = "{{{baseName}}}"; + {{#vendorExtensions.x-utf8-base64-url-encoded-as-string}} + String {{{paramName}}}AsBase64EncodedQueryParam = {{{paramName}}} == null ? null : new String(java.util.Base64.getUrlEncoder().encode({{paramName}}.getBytes(java.nio.charset.StandardCharsets.UTF_8)), java.nio.charset.StandardCharsets.UTF_8); + {{#collectionFormat}} + localVarQueryParams.addAll(ApiClient.parameterToPairs("{{{collectionFormat}}}", "{{baseName}}", {{paramName}}AsBase64EncodedQueryParam)); + {{/collectionFormat}} + {{^collectionFormat}} + {{^collectionFormat}} + {{#isDeepObject}} + if ({{paramName}} != null) { + {{#isArray}} + for (int i=0; i < {{paramName}}.size(); i++) { + localVarQueryStringJoiner.add({{paramName}}.get(i).toUrlQueryString(String.format("{{baseName}}[%d]", i))); + } + {{/isArray}} + {{^isArray}} + localVarQueryStringJoiner.add({{paramName}}.toUrlQueryString("{{baseName}}")); + {{/isArray}} + } + {{/isDeepObject}} + {{^isDeepObject}} + {{#isExplode}} + {{#hasVars}} + {{#vars}} + {{#isArray}} + localVarQueryParams.addAll(ApiClient.parameterToPairs("multi", "{{baseName}}", {{paramName}}.{{getter}}())); + {{/isArray}} + {{^isArray}} + localVarQueryParams.addAll(ApiClient.parameterToPairs("{{baseName}}", {{paramName}}.{{getter}}())); + {{/isArray}} + {{/vars}} + {{/hasVars}} + {{^hasVars}} + {{#isModel}} + localVarQueryStringJoiner.add({{paramName}}.toUrlQueryString()); + {{/isModel}} + {{^isModel}} + localVarQueryParams.addAll(ApiClient.parameterToPairs("{{baseName}}", {{paramName}}AsBase64EncodedQueryParam)); + {{/isModel}} + {{/hasVars}} + {{/isExplode}} + {{^isExplode}} + localVarQueryParams.addAll(ApiClient.parameterToPairs("{{baseName}}", {{paramName}})); + {{/isExplode}} + {{/isDeepObject}} + {{/collectionFormat}} + {{/collectionFormat}} + {{/vendorExtensions.x-utf8-base64-url-encoded-as-string}} + {{^vendorExtensions.x-utf8-base64-url-encoded-as-string}} + {{#collectionFormat}} + localVarQueryParams.addAll(ApiClient.parameterToPairs("{{{collectionFormat}}}", "{{baseName}}", {{paramName}})); + {{/collectionFormat}} + {{^collectionFormat}} + {{#isDeepObject}} + if ({{paramName}} != null) { + {{#isArray}} + for (int i=0; i < {{paramName}}.size(); i++) { + localVarQueryStringJoiner.add({{paramName}}.get(i).toUrlQueryString(String.format("{{baseName}}[%d]", i))); + } + {{/isArray}} + {{^isArray}} + localVarQueryStringJoiner.add({{paramName}}.toUrlQueryString("{{baseName}}")); + {{/isArray}} + } + {{/isDeepObject}} + {{^isDeepObject}} + {{#isExplode}} + {{#hasVars}} + {{#vars}} + {{#isArray}} + localVarQueryParams.addAll(ApiClient.parameterToPairs("multi", "{{baseName}}", {{paramName}}.{{getter}}())); + {{/isArray}} + {{^isArray}} + localVarQueryParams.addAll(ApiClient.parameterToPairs("{{baseName}}", {{paramName}}.{{getter}}())); + {{/isArray}} + {{/vars}} + {{/hasVars}} + {{^hasVars}} + {{#isModel}} + localVarQueryStringJoiner.add({{paramName}}.toUrlQueryString()); + {{/isModel}} + {{^isModel}} + localVarQueryParams.addAll(ApiClient.parameterToPairs("{{baseName}}", {{paramName}})); + {{/isModel}} + {{/hasVars}} + {{/isExplode}} + {{^isExplode}} + localVarQueryParams.addAll(ApiClient.parameterToPairs("{{baseName}}", {{paramName}})); + {{/isExplode}} + {{/isDeepObject}} + {{/collectionFormat}} + {{/vendorExtensions.x-utf8-base64-url-encoded-as-string}} + {{/queryParams}} + + if (!localVarQueryParams.isEmpty() || localVarQueryStringJoiner.length() != 0) { + {{javaUtilPrefix}}StringJoiner queryJoiner = new {{javaUtilPrefix}}StringJoiner("&"); + localVarQueryParams.forEach(p -> queryJoiner.add(p.getName() + '=' + p.getValue())); + if (localVarQueryStringJoiner.length() != 0) { + queryJoiner.add(localVarQueryStringJoiner.toString()); + } + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath + '?' + queryJoiner.toString())); + } else { + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); + } + {{/hasQueryParams}} + {{^hasQueryParams}} + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); + {{/hasQueryParams}} + + {{#headerParams}} + if ({{paramName}} != null) { + localVarRequestBuilder.header("{{baseName}}", {{paramName}}.toString()); + } + {{/headerParams}} + {{#bodyParam}} + localVarRequestBuilder.header("Content-Type", "{{#hasConsumes}}{{#consumes}}{{#-first}}{{mediaType}}{{/-first}}{{/consumes}}{{/hasConsumes}}{{#hasConsumes}}{{^consumes}}application/json{{/consumes}}{{/hasConsumes}}{{^hasConsumes}}application/json{{/hasConsumes}}"); + {{/bodyParam}} + localVarRequestBuilder.header("Accept", "{{#hasProduces}}{{#produces}}{{mediaType}}{{^-last}}, {{/-last}}{{/produces}}{{/hasProduces}}{{#hasProduces}}{{^produces}}application/json{{/produces}}{{/hasProduces}}{{^hasProduces}}application/json{{/hasProduces}}"); + + {{#bodyParam}} + {{#isString}} + localVarRequestBuilder.method("{{httpMethod}}", HttpRequest.BodyPublishers.ofString({{paramName}})); + {{/isString}} + {{^isString}} + try { + byte[] localVarPostBody = memberVarObjectMapper.writeValueAsBytes({{paramName}}); + localVarRequestBuilder.method("{{httpMethod}}", HttpRequest.BodyPublishers.ofByteArray(localVarPostBody)); + } catch (IOException e) { + throw new ApiException(e); + } + {{/isString}} + {{/bodyParam}} + {{^bodyParam}} + {{#hasFormParams}} + {{#isMultipart}} + MultipartEntityBuilder multiPartBuilder = MultipartEntityBuilder.create(); + boolean hasFiles = false; + {{#formParams}} + {{#isArray}} + for (int i=0; i < {{paramName}}.size(); i++) { + multiPartBuilder.addTextBody("{{{baseName}}}", {{paramName}}.get(i).toString()); + } + {{/isArray}} + {{^isArray}} + {{#isFile}} + multiPartBuilder.addBinaryBody("{{{baseName}}}", {{paramName}}); + hasFiles = true; + {{/isFile}} + {{^isFile}} + multiPartBuilder.addTextBody("{{{baseName}}}", {{paramName}}.toString()); + {{/isFile}} + {{/isArray}} + {{/formParams}} + HttpEntity entity = multiPartBuilder.build(); + HttpRequest.BodyPublisher formDataPublisher; + if (hasFiles) { + Pipe pipe; + try { + pipe = Pipe.open(); + } catch (IOException e) { + throw new RuntimeException(e); + } + new Thread(() -> { + try (OutputStream outputStream = Channels.newOutputStream(pipe.sink())) { + entity.writeTo(outputStream); + } catch (IOException e) { + e.printStackTrace(); + } + }).start(); + formDataPublisher = HttpRequest.BodyPublishers.ofInputStream(() -> Channels.newInputStream(pipe.source())); + } else { + ByteArrayOutputStream formOutputStream = new ByteArrayOutputStream(); + try { + entity.writeTo(formOutputStream); + } catch (IOException e) { + throw new RuntimeException(e); + } + formDataPublisher = HttpRequest.BodyPublishers + .ofInputStream(() -> new ByteArrayInputStream(formOutputStream.toByteArray())); + } + localVarRequestBuilder + .header("Content-Type", entity.getContentType().getValue()) + .method("{{httpMethod}}", formDataPublisher); + {{/isMultipart}} + {{^isMultipart}} + List formValues = new ArrayList<>(); + {{#formParams}} + {{#isArray}} + for (int i=0; i < {{paramName}}.size(); i++) { + if ({{paramName}}.get(i) != null) { + formValues.add(new BasicNameValuePair("{{{baseName}}}", {{paramName}}.get(i).toString())); + } + } + {{/isArray}} + {{^isArray}} + if ({{paramName}} != null) { + formValues.add(new BasicNameValuePair("{{{baseName}}}", {{paramName}}.toString())); + } + {{/isArray}} + {{/formParams}} + HttpEntity entity = new UrlEncodedFormEntity(formValues, java.nio.charset.StandardCharsets.UTF_8); + ByteArrayOutputStream formOutputStream = new ByteArrayOutputStream(); + try { + entity.writeTo(formOutputStream); + } catch (IOException e) { + throw new RuntimeException(e); + } + localVarRequestBuilder + .header("Content-Type", entity.getContentType().getValue()) + .method("{{httpMethod}}", HttpRequest.BodyPublishers + .ofInputStream(() -> new ByteArrayInputStream(formOutputStream.toByteArray()))); + {{/isMultipart}} + {{/hasFormParams}} + {{^hasFormParams}} + localVarRequestBuilder.method("{{httpMethod}}", HttpRequest.BodyPublishers.noBody()); + {{/hasFormParams}} + {{/bodyParam}} + if (memberVarReadTimeout != null) { + localVarRequestBuilder.timeout(memberVarReadTimeout); + } + if (memberVarInterceptor != null) { + memberVarInterceptor.accept(localVarRequestBuilder); + } + return localVarRequestBuilder; + } + {{#vendorExtensions.x-group-parameters}} + {{#hasParams}} + + public static final class API{{operationId}}Request { + {{#requiredParams}} + private {{{dataType}}} {{paramName}}; // {{description}} (required) + {{/requiredParams}} + {{#optionalParams}} + private {{{dataType}}} {{paramName}}; // {{description}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/isContainer}} + {{/optionalParams}} + + private API{{operationId}}Request(Builder builder) { + {{#requiredParams}} + this.{{paramName}} = builder.{{paramName}}; + {{/requiredParams}} + {{#optionalParams}} + this.{{paramName}} = builder.{{paramName}}; + {{/optionalParams}} + } + {{#allParams}} + public {{{dataType}}} {{paramName}}() { + return {{paramName}}; + } + {{/allParams}} + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + {{#requiredParams}} + private {{{dataType}}} {{paramName}}; + {{/requiredParams}} + {{#optionalParams}} + private {{{dataType}}} {{paramName}}; + {{/optionalParams}} + + {{#allParams}} + public Builder {{paramName}}({{{dataType}}} {{paramName}}) { + this.{{paramName}} = {{paramName}}; + return this; + } + {{/allParams}} + public API{{operationId}}Request build() { + return new API{{operationId}}Request(this); + } + } + } + + {{/hasParams}} + {{/vendorExtensions.x-group-parameters}} + {{/operation}} +} +{{/operations}} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-client-native/templates/libraries/native/licenseInfo.mustache b/basyx.submodelregistry/basyx.submodelregistry-client-native/templates/libraries/native/licenseInfo.mustache new file mode 100644 index 000000000..7e24a20c9 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-client-native/templates/libraries/native/licenseInfo.mustache @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basemodel/.gitignore b/basyx.submodelregistry/basyx.submodelregistry-service-basemodel/.gitignore new file mode 100644 index 000000000..ae3c17260 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basemodel/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basemodel/README.md b/basyx.submodelregistry/basyx.submodelregistry-service-basemodel/README.md new file mode 100644 index 000000000..85fee100b --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basemodel/README.md @@ -0,0 +1,6 @@ +# Submodel Registry Service Base Model + +The project provides model classes generated based on the OpenAPI description. It is referenced in the base server project POM as 'provided'. Thus, you can either reference the model again in your server implementation and include these model classes or implement your model classes that better fit your needs for the storage model classes. For example, if you want to have specific model class annotations. + + + diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basemodel/open-api/.gitkeep b/basyx.submodelregistry/basyx.submodelregistry-service-basemodel/open-api/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basemodel/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-service-basemodel/pom.xml new file mode 100644 index 000000000..af8b815b0 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basemodel/pom.xml @@ -0,0 +1,118 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry + ${revision} + .. + + + basyx.submodelregistry-service-basemodel + + jar + + + ${project.basedir}/${openapi.folder.name} + ${openapi.folder}/${openapi.name} + + + + ${generated.folder}/java + + + maven-clean-plugin + + + + ${project.basedir}/${generated.folder} + + **/.gitkeep + + false + + + ${openapi.folder} + + .gitkeep + + false + + + + + + de.dfki.cos.basys.common + jsonpatch-maven-plugin + + + generate-sources + + jsonpatch-maven-plugin + + + ${project.basedir}/../${openapi.folder.name}/${openapi.base.name} + ${project.basedir}/../${openapi.folder.name}/${patch.base-extensions.name} + ${openapi.result.file} + + + + + + org.openapitools + openapi-generator-maven-plugin + + + + generate + + + true + false + false + ${openapi.result.file} + spring + spring-boot + ${project.basedir}/${generated.folder} + + + true + java8 + java + is + true + true + true + org.eclipse.digitaltwin.basyx.submodelregistry.model + array=ArrayList,map=HashMap + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + true + + + + + + + org.openapitools + jackson-databind-nullable + + + org.springdoc + springdoc-openapi-ui + + + org.springframework.boot + spring-boot-starter-validation + + + \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/README.md b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/README.md new file mode 100644 index 000000000..1f9f8618b --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/README.md @@ -0,0 +1,14 @@ +# Submodel Registry Service Base Tests + +This project offers test utility classes that you can use in your storage or integration tests. + +We use an extra project here instead of *test-jar* maven artifact generation, as it is the [preferred way](https://maven.apache.org/plugins/maven-jar-plugin/examples/create-test-jar.html) of providing test artifacts. + +Have a look at the mongoDb-storage project or the release projects to see how the abstract test classes defined here can be used. The classes provide good test coverage. You can extend them without writing additional test cases for your storage. + +Use [testcontainers](https://www.testcontainers.org/) for your integration or storage tests. + +The integration test of the [kafka-mongodb release](../basyx.submodelregistry-service-release-kafka-mongodb/Readme.md) project starts an Apache Kafka and an MongoDb instance using testcontainers. Don't forget to start a docker daemon before running the tests. + + + diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/pom.xml new file mode 100644 index 000000000..e978d7167 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry + ${revision} + .. + + + basyx.submodelregistry-service-basetests + jar + + + 2020.0.4 + org.eclipse.digitaltwin.basyx.submodelregistry.service.OpenApiGeneratorApplication + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + + org.projectlombok + lombok + provided + true + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-client-native + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-basemodel + provided + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service + + + org.springframework.boot + spring-boot-starter-test + + + org.testcontainers + testcontainers + ${testcontainers.version} + + + + junit + junit + + + org.junit.vintage + junit-vintage-engine + + + org.hamcrest + hamcrest-core + + + + + \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/BaseInterfaceTest.java b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/BaseInterfaceTest.java new file mode 100644 index 000000000..57f5412a6 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/BaseInterfaceTest.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.tests; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.events.RegistryEvent; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.events.RegistryEventSink; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.CursorResult; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.PaginationInfo; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.RegistrationEventSendingSubmodelRegistryStorage; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.SubmodelRegistryStorage; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; +import org.mockito.InOrder; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) +public abstract class BaseInterfaceTest { + + protected static final String SM_ID_5 = "sm5"; + protected static final String SM_ID_3 = "sm3"; + protected static final String SM_ID_2 = "sm2"; + protected static final String UNKNOWN_SM_ID = "unknown"; + + private RegistryEventSink eventSink = Mockito.mock(RegistryEventSink.class); + + @Autowired + public SubmodelRegistryStorage baseStorage; + + protected RegistrationEventSendingSubmodelRegistryStorage storage; + + @Rule + public TestResourcesLoader testResourcesLoader = new TestResourcesLoader(); + + protected RegistryEventSink getEventSink() { + return eventSink; + } + + @Before + public void setUp() throws IOException { + storage = new RegistrationEventSendingSubmodelRegistryStorage(baseStorage, eventSink); + List submodels = testResourcesLoader.loadRepositoryDefinition(SubmodelDescriptor.class); + submodels.forEach(baseStorage::insertSubmodelDescriptor); + } + + @After + public void tearDown() { + storage.clear(); + } + + protected void assertNullPointerThrown(ThrowingCallable callable) { + Throwable th = Assertions.catchThrowable(callable); + assertThat(th).isInstanceOf(NullPointerException.class); + verifyNoEventSent(); + } + + protected void verifyEventsSent() throws IOException { + List events = testResourcesLoader.loadEvents(); + + Mockito.verify(eventSink, Mockito.times(events.size())).consumeEvent(ArgumentMatchers.any(RegistryEvent.class)); + + InOrder inOrder = Mockito.inOrder(eventSink); + for (RegistryEvent eachEvent : events) { + inOrder.verify(eventSink).consumeEvent(eachEvent); + } + } + + protected void verifyNoEventSent() { + Mockito.verify(eventSink, Mockito.never()).consumeEvent(ArgumentMatchers.any(RegistryEvent.class)); + } + + protected List getAllSubmodels() { + return storage.getAllSubmodelDescriptors(new PaginationInfo(null, null)).getResult(); + } + + protected CursorResult getAllSubmodelsWithPagination(int limit, String cursor) { + return storage.getAllSubmodelDescriptors(new PaginationInfo(limit, cursor)); + } + +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/ExtensionsTest.java b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/ExtensionsTest.java new file mode 100644 index 000000000..176d2ddfe --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/ExtensionsTest.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.tests; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Set; + +import org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.events.RegistryEvent; +import org.junit.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; + + +public abstract class ExtensionsTest extends BaseInterfaceTest { + + @Test + public void whenDeleteAllSubmodels_thenEventsAreSendAndSubmodelsRemoved() { + List oldState = getAllSubmodels(); + assertThat(oldState).isNotEmpty(); + Set idsOfRemovedDescriptors = storage.clear(); + // listener is invoked for each removal + Mockito.verify(getEventSink(), Mockito.times(idsOfRemovedDescriptors.size())).consumeEvent(ArgumentMatchers.any(RegistryEvent.class)); + List newState = getAllSubmodels(); + assertThat(newState).isEmpty(); + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/SubmodelRegistryStorageTest.java b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/SubmodelRegistryStorageTest.java new file mode 100644 index 000000000..878283786 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/SubmodelRegistryStorageTest.java @@ -0,0 +1,206 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.tests; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertThrows; + +import java.io.IOException; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.eclipse.digitaltwin.basyx.submodelregistry.model.LangStringTextType; +import org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.errors.SubmodelAlreadyExistsException; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.errors.SubmodelNotFoundException; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.CursorResult; +import org.junit.Test; + +public abstract class SubmodelRegistryStorageTest extends ExtensionsTest { + + @Test + public void whenRegisterSubmodelDescriptorNullModel_thenNullPointer() { + assertNullPointerThrown(() -> storage.insertSubmodelDescriptor(null)); + } + + @Test + public void whenRegisterSubmodelDescriptorAndWasAlreadyPresent_thenElementIsOverridden() throws IOException { + List initialState = getAllSubmodels(); + List expected = testResourcesLoader.loadList(SubmodelDescriptor.class); + assertThat(initialState).isNotEqualTo(expected); + SubmodelDescriptor toAdd = new SubmodelDescriptor(SM_ID_2, List.of()).addDescriptionItem(new LangStringTextType("de-DE", "Overridden")); + storage.replaceSubmodelDescriptor(SM_ID_2, toAdd); + List newState = getAllSubmodels(); + assertThat(newState).asList().isNotEqualTo(initialState).containsExactlyInAnyOrderElementsOf(expected); + verifyEventsSent(); + } + + public void whenReplaceSubmodelDescriptorAdnNotPresent_thenExceptionIsTrown() { + assertThrows(SubmodelNotFoundException.class, () -> storage.replaceSubmodelDescriptor(UNKNOWN_SM_ID, new SubmodelDescriptor(UNKNOWN_SM_ID, List.of()))); + + } + + @Test + public void whenRegisterSubmodelDescriptorAndWasNotAlreadyPresent_thenElementIsAdded() throws IOException { + List initialState = getAllSubmodels(); + List expected = testResourcesLoader.loadList(SubmodelDescriptor.class); + assertThat(initialState).isNotEqualTo(expected); + SubmodelDescriptor toAdd = new SubmodelDescriptor(SM_ID_3, List.of()); + storage.insertSubmodelDescriptor(toAdd); + List newState = getAllSubmodels(); + + assertThat(newState).asList().isNotEqualTo(initialState).containsExactlyInAnyOrderElementsOf(expected); + verifyEventsSent(); + } + + @Test + public void whenUnregisterSubmodelDescriptorNullSubmodelId_thenNullPointer() { + assertNullPointerThrown(() -> storage.removeSubmodelDescriptor(null)); + } + + @Test + public void whenGetAllSubmodelDescriptors_thenAll() throws IOException { + Collection found = getAllSubmodels(); + List expected = testResourcesLoader.loadList(SubmodelDescriptor.class); + assertThat(found).containsExactlyInAnyOrderElementsOf(expected); + verifyNoEventSent(); + } + + @Test + public void whenGetAllSubmodelDescriptorsOverTwoPages_thenReturnPageStepByStep() throws IOException { + CursorResult firstResult = getAllSubmodelsWithPagination(2, null); + List expected = testResourcesLoader.loadRepositoryDefinition(SubmodelDescriptor.class); + + CursorResult secondResult = getAllSubmodelsWithPagination(2, firstResult.getCursor()); + + assertThat(firstResult.getResult()).containsExactlyInAnyOrderElementsOf(expected.subList(0, 2)); + assertThat(secondResult.getResult()).containsExactlyInAnyOrderElementsOf(expected.subList(2, 4)); + + if (secondResult.getCursor() != null) { // implementation specific + CursorResult thirdResult = getAllSubmodelsWithPagination(2, secondResult.getCursor()); + assertThat(thirdResult.getResult()).isEmpty(); + } + verifyNoEventSent(); + } + + @Test + public void whenGetAllSubmodelDescriptorsAndEmptyRepo_thenEmptyList() { + Collection found = getAllSubmodels(); + assertThat(found).isEmpty(); + verifyNoEventSent(); + } + + @Test + public void whenGetSubmodelDescritorByIdAndIdIsNull_thenNullPointer() { + assertNullPointerThrown(() -> storage.getSubmodelDescriptor(null)); + } + + @Test + public void whenGetSubmodelByIdAndUnknown_thenThrowNotFound() { + assertThrows(SubmodelNotFoundException.class, () -> storage.getSubmodelDescriptor(UNKNOWN_SM_ID)); + verifyNoEventSent(); + } + + @Test + public void whenGetSubmodelDescritorByIdAndAvailable_thenGotResult() throws IOException { + SubmodelDescriptor result = storage.getSubmodelDescriptor(SM_ID_2); + SubmodelDescriptor expected = testResourcesLoader.load(SubmodelDescriptor.class); + assertThat(result).isEqualTo(expected); + verifyNoEventSent(); + } + + @Test + public void whenRegisterAssetAdministrationShellDescriptorNullArg_thenNullPointer() { + assertNullPointerThrown(() -> storage.replaceSubmodelDescriptor(null, null)); + } + + @Test + public void whenRegisterSubmodelDescriptor_thenStored() throws IOException { + List initialState = getAllSubmodels(); + List expected = testResourcesLoader.loadList(SubmodelDescriptor.class); + assertThat(initialState).isNotEqualTo(expected); + SubmodelDescriptor testResource = new SubmodelDescriptor("new", List.of()); + storage.insertSubmodelDescriptor(testResource); + List newState = getAllSubmodels(); + assertThat(newState).asList().isNotEqualTo(initialState).containsExactlyInAnyOrderElementsOf(expected); + verifyEventsSent(); + } + + @Test + public void whenUnregisterSubmodelDescriptorByIdAndNullId_thenThrowNotFoundAndNoChanges() { + List initialState = getAllSubmodels(); + assertThrows(NullPointerException.class, () -> storage.removeSubmodelDescriptor(null)); + List currentState = getAllSubmodels(); + assertThat(currentState).asList().containsExactlyInAnyOrderElementsOf(initialState); + verifyNoEventSent(); + } + + @Test + public void whenUnregisterSubmodelDescriptorById_thenReturnTrueAndEntryRemoved() throws IOException { + List initialState = getAllSubmodels(); + List expected = testResourcesLoader.loadList(SubmodelDescriptor.class); + storage.removeSubmodelDescriptor(SM_ID_2); + List currentState = getAllSubmodels(); + assertThat(currentState).asList().isNotEqualTo(initialState).containsExactlyInAnyOrderElementsOf(expected); + verifyEventsSent(); + } + + @Test + public void whenUnregisterSubmodelDescriptorByIdAndIdUnknon_thenReturnFalsAndNoChanges() { + List initialState = getAllSubmodels(); + assertThrows(SubmodelNotFoundException.class, () -> storage.removeSubmodelDescriptor(UNKNOWN_SM_ID)); + List currentState = getAllSubmodels(); + assertThat(currentState).asList().containsExactlyInAnyOrderElementsOf(initialState); + verifyNoEventSent(); + } + + @Test + public void whenRegistrationUpdateForNewId_AvailableUnderNewIdAndTwoEventsFired() throws IOException { + List initialState = getAllSubmodels(); + SubmodelDescriptor descr = initialState.stream().filter(a -> a.getId().equals(SM_ID_2)).findAny().orElseThrow(); + + SubmodelDescriptor copy = new SubmodelDescriptor(SM_ID_3, new LinkedList<>(descr.getEndpoints())); + copy.idShort(descr.getIdShort()); + + storage.replaceSubmodelDescriptor(SM_ID_2, copy); + List currentState = getAllSubmodels(); + List expected = testResourcesLoader.loadList(SubmodelDescriptor.class); + + assertThat(currentState).asList().isNotEqualTo(initialState).containsExactlyInAnyOrderElementsOf(expected); + verifyEventsSent(); + } + + @Test + public void whenTryToReplaceUnknownSubmodel_thenThrowException() { + SubmodelDescriptor descr = new SubmodelDescriptor(SM_ID_5, List.of()); + assertThrows(SubmodelNotFoundException.class, () -> storage.replaceSubmodelDescriptor(UNKNOWN_SM_ID, descr)); + } + + @Test + public void whenInsertSubmodelAndSubmodelAlreadyAvailable_thenThrowException() { + assertThrows(SubmodelAlreadyExistsException.class, () -> storage.insertSubmodelDescriptor(new SubmodelDescriptor("sm1", List.of()))); + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/TestResourcesLoader.java b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/TestResourcesLoader.java new file mode 100644 index 000000000..c74a43af2 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/TestResourcesLoader.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.tests; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.eclipse.digitaltwin.basyx.submodelregistry.service.events.RegistryEvent; +import org.junit.rules.TestName; +import org.junit.runner.Description; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; + +public class TestResourcesLoader extends TestName { + + private static final String JSON_FILE_ENDING = ".json"; + + private String packageName; + + private ObjectMapper mapper; + + public TestResourcesLoader(String packageName, ObjectMapper mapper) { + this.packageName = packageName; + this.mapper = mapper; + } + + public TestResourcesLoader() { + this(null, new ObjectMapper()); + } + + @Override + protected void starting(Description d) { + super.starting(d); + if (packageName == null) { + packageName = d.getTestClass().getPackageName(); + } + } + + public List loadRepositoryDefinition(Class cls) throws IOException { + String path = getTestRepositoryPath(); + return loadValue(path, mapper.readerForListOf(cls)); + } + + public List loadList(Class cls) throws IOException { + return loadList(cls, null); + } + + public List loadList(Class cls, String suffix) throws IOException { + return load(mapper.readerForListOf(cls), suffix != null ? "_" + suffix : ""); + } + + public T load(Class cls) throws IOException { + return load(cls, null); + } + + public T load(Class cls, String suffix) throws IOException { + return load(mapper.readerFor(cls), suffix != null ? "_" + suffix : ""); + } + + public List loadEvents() throws IOException { + return load(mapper.readerForListOf(RegistryEvent.class), "_events"); + } + + private String getTestRepositoryPath() { + String repoPath = getMethodName() + "_repo" + JSON_FILE_ENDING; + String basedOnTestClass = basedOnPackageName(repoPath); + if (getClass().getResource(basedOnTestClass) != null) { + return repoPath; + } + return "default_repository.json"; + } + + private String basedOnPackageName(String relativePath) { + return "/" + packageName.replace('.', '/') + "/" + relativePath; + } + + private T load(ObjectReader reader, String suffix) throws IOException { + String path = getMethodName() + suffix + JSON_FILE_ENDING; + return loadValue(path, reader); + } + + private T loadValue(String path, ObjectReader reader) throws IOException { + path = basedOnPackageName(path); + try (InputStream in = getClass().getResourceAsStream(path); BufferedInputStream bIn = new BufferedInputStream(in)) { + return reader.readValue(bIn); + } + } + + public String loadJsonAsString() throws IOException { + String suffixPath = getMethodName() + JSON_FILE_ENDING; + String path = basedOnPackageName(suffixPath); + try (InputStream in = getClass().getResourceAsStream(path); BufferedInputStream bIn = new BufferedInputStream(in)) { + byte[] allBytes = bIn.readAllBytes(); + return new String(allBytes, StandardCharsets.UTF_8); + } + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/BaseEventListener.java b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/BaseEventListener.java new file mode 100644 index 000000000..bc53cb9a1 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/BaseEventListener.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.tests.integration; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.eclipse.digitaltwin.basyx.submodelregistry.service.events.RegistryEvent; +import org.springframework.beans.factory.annotation.Autowired; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +public abstract class BaseEventListener { + + private LinkedBlockingQueue messageQueue = new LinkedBlockingQueue<>(); + + @Autowired + private ObjectMapper mapper; + + public boolean offer(String message) { + return messageQueue.offer(message); + } + + public void reset() { + try { + while (messageQueue.poll(1, TimeUnit.SECONDS) != null) + ; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new EventListenerException(e); + } + } + + public void assertNoAdditionalMessage() { + try { + String message = messageQueue.poll(1, TimeUnit.SECONDS); + if (message != null) { + throw new EventListenerException("Got additional message: " + message); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new EventListenerException(e); + } + } + + public RegistryEvent poll() { + try { + String message = messageQueue.poll(15, TimeUnit.SECONDS); + if (message == null) { + throw new EventListenerException("timeout"); + } + return mapper.readValue(message, RegistryEvent.class); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new EventListenerException(e); + } catch (JsonProcessingException e) { + throw new EventListenerException(e); + } + } + + public static final class EventListenerException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public EventListenerException(Throwable e) { + super(e); + } + + public EventListenerException(String msg) { + super(msg); + } + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/BaseIntegrationTest.java b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/BaseIntegrationTest.java new file mode 100644 index 000000000..3f2541918 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/BaseIntegrationTest.java @@ -0,0 +1,337 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.tests.integration; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.assertj.core.api.SoftAssertionsProvider.ThrowingRunnable; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.ApiException; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.ApiResponse; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.api.SubmodelRegistryApi; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.AdministrativeInformation; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.DataSpecificationContent; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.DataSpecificationIec61360; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.DataTypeIec61360; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.EmbeddedDataSpecification; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.Endpoint; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.Extension; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.GetSubmodelDescriptorsResult; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.Key; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.KeyTypes; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.LangStringDefinitionTypeIec61360; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.LangStringNameType; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.LangStringPreferredNameTypeIec61360; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.LangStringShortNameTypeIec61360; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.LangStringTextType; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.LevelType; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.ProtocolInformation; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.ProtocolInformationSecurityAttributes; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.ProtocolInformationSecurityAttributes.TypeEnum; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.Reference; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.ReferenceTypes; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.ServiceDescription; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.ServiceDescription.ProfilesEnum; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.events.RegistryEvent; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.events.RegistryEvent.EventType; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.tests.TestResourcesLoader; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.junit4.SpringRunner; + +import com.fasterxml.jackson.databind.ObjectMapper; + +@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@RunWith(SpringRunner.class) +public abstract class BaseIntegrationTest { + + private static final String LANG_DE_DE = "de-DE"; + + private static final int BAD_REQUEST = 400; + + private static final String IDENTIFICATION_9 = "identification_9"; + + private static final String IDENTIFICATION_7 = "identification_7"; + + private static final int DELETE_ALL_TEST_INSTANCE_COUNT = 50; + + private static final int NO_CONTENT = 204; + + private static final int CREATED = 201; + + private static final int OK = 200; + + private static final int NOT_FOUND = 404; + + @Value("${local.server.port}") + private int port; + + @Rule + public TestResourcesLoader resourceLoader = new TestResourcesLoader(BaseIntegrationTest.class.getPackageName(), new ObjectMapper()); + + @Autowired + private BaseEventListener listener; + + protected SubmodelRegistryApi api; + + @Before + public void initClient() throws ApiException { + api = new SubmodelRegistryApi("http", "127.0.0.1", port); + api.deleteAllSubmodelDescriptors(); + listener.assertNoAdditionalMessage(); + } + + @After + public void cleanup() throws ApiException { + api.deleteAllSubmodelDescriptors(); + } + + @Test + public void whenGetDescription_thenDescriptionIsReturned() throws ApiException { + ApiResponse entity = api.getDescriptionWithHttpInfo(); + assertThat(entity.getStatusCode()).isEqualTo(OK); + List profiles = entity.getData().getProfiles(); + assertThat(profiles).asList().hasSize(1); + assertThat(profiles).asList().containsExactlyInAnyOrder(ProfilesEnum.SUBMODELREGISTRYSERVICESPECIFICATION_SSP_001); + } + + @Test + public void whenWritingParallel_transactionManagementWorks() throws ApiException { + IntStream.iterate(0, i -> i + 1).limit(300).parallel().forEach(this::postSubmodel); + assertThat(api.getAllSubmodelDescriptors(null, null).getResult()).hasSize(300); + } + + private void postSubmodel(int id) { + SubmodelDescriptor descr = new SubmodelDescriptor().id(id+"").addEndpointsItem(defaultClientEndpoint()); + try { + api.postSubmodelDescriptor(descr); + } catch (ApiException e) { + throw new RuntimeException(e); + } + } + + @Test + public void whenDeleteAll_thenAllDescriptorsAreRemoved() throws ApiException { + for (int i = 0; i < DELETE_ALL_TEST_INSTANCE_COUNT; i++) { + SubmodelDescriptor descr = new SubmodelDescriptor(); + String id = "id_" + i; + descr.setId(id); + descr.addEndpointsItem(defaultClientEndpoint()); + ApiResponse response = api.postSubmodelDescriptorWithHttpInfo(descr); + assertThat(response.getStatusCode()).isEqualTo(201); + // we need a mapping here + assertThatEventWasSend(RegistryEvent.builder().id(id).submodelDescriptor(new org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor(id, List.of(defaultServerEndpoint()))).type(EventType.SUBMODEL_REGISTERED).build()); + } + List all = api.getAllSubmodelDescriptors(null, null).getResult(); + assertThat(all.size()).isEqualTo(DELETE_ALL_TEST_INSTANCE_COUNT); + + api.deleteAllSubmodelDescriptors(); + + all = api.getAllSubmodelDescriptors(null, null).getResult(); + assertThat(all).isEmpty(); + + HashSet events = new HashSet<>(); + // we do not have a specific order, so read all events first + for (int i = 0; i < DELETE_ALL_TEST_INSTANCE_COUNT; i++) { + events.add(listener.poll()); + } + for (int i = 0; i < DELETE_ALL_TEST_INSTANCE_COUNT; i++) { + assertThat(events.remove(RegistryEvent.builder().id("id_" + i).type(EventType.SUBMODEL_UNREGISTERED).build())).isTrue(); + } + listener.assertNoAdditionalMessage(); + } + + @Test + public void whenCreateAndDeleteDescriptors_thenAllDescriptorsAreRemoved() throws IOException, InterruptedException, TimeoutException, ApiException { + List deployed = initialize(); + List all = api.getAllSubmodelDescriptors(null, null).getResult(); + assertThat(all).containsExactlyInAnyOrderElementsOf(deployed); + + for (SubmodelDescriptor eachDescriptor : all) { + deleteSubmodelDescriptor(eachDescriptor.getId()); + } + + all = api.getAllSubmodelDescriptors(null, null).getResult(); + assertThat(all).isEmpty(); + + listener.assertNoAdditionalMessage(); + } + + + @Test + public void whenInvalidInput_thenSuccessfullyValidated() throws Exception { + initialize(); + assertThrowsApiException(() -> api.postSubmodelDescriptor(null), BAD_REQUEST); + assertThrowsApiException(() -> api.putSubmodelDescriptorById(null, null), BAD_REQUEST); + assertThrowsApiException(() -> api.putSubmodelDescriptorById("sm", null), BAD_REQUEST); + SubmodelDescriptor descriptor = new SubmodelDescriptor(); + descriptor.setIdShort("shortId"); + assertThrowsApiException(() -> api.postSubmodelDescriptor(descriptor), BAD_REQUEST); + + descriptor.setId("identification"); + descriptor.addEndpointsItem(defaultClientEndpoint()); + int status = api.postSubmodelDescriptorWithHttpInfo(descriptor).getStatusCode(); + assertThat(status).isEqualTo(201); + assertThatEventWasSend(RegistryEvent.builder().id(descriptor.getId()).submodelDescriptor(new org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor(descriptor.getId(), List.of(defaultServerEndpoint())).idShort(descriptor.getIdShort())) + .type(EventType.SUBMODEL_REGISTERED).build()); + } + + @Test + public void whenPutSubmodelDescriptorDifferentId_thenMoved() throws Exception { + initialize(); + SubmodelDescriptor descr = new SubmodelDescriptor().id(IDENTIFICATION_9).addEndpointsItem(defaultClientEndpoint()); + ApiResponse putResult = api.putSubmodelDescriptorByIdWithHttpInfo(IDENTIFICATION_7, descr); + assertThat(putResult.getStatusCode()).isEqualTo(NO_CONTENT); + assertThrowsApiException(() -> api.getSubmodelDescriptorByIdWithHttpInfo(IDENTIFICATION_7), NOT_FOUND); + ApiResponse getResult = api.getSubmodelDescriptorByIdWithHttpInfo(IDENTIFICATION_9); + assertThat(getResult.getStatusCode()).isEqualTo(OK); + assertThat(descr).isEqualTo(getResult.getData()); + } + + + @Test + public void whenPutUnknownSubmodelDescriptor_thenNotFound() throws Exception { + initialize(); + SubmodelDescriptor descr = new SubmodelDescriptor().id("unknown").addEndpointsItem(defaultClientEndpoint()); + assertThrowsApiException(() -> api.putSubmodelDescriptorById(descr.getId(), descr), NOT_FOUND); + + } + + @Test + public void whenUseSubmodelDescriptorPagination_thenUseRefetching() throws IOException, InterruptedException, TimeoutException, ApiException { + List postedDescriptors = initialize(); + List postedDescriptorsSorted = postedDescriptors.stream().sorted(Comparator.comparing(SubmodelDescriptor::getId)).collect(Collectors.toList()); + assertThat(postedDescriptors).hasSize(5); + + GetSubmodelDescriptorsResult result0 = api.getAllSubmodelDescriptors(2, null); + List body0 = result0.getResult(); + assertThat(body0).hasSize(2); + assertThat(postedDescriptorsSorted.get(0)).isEqualTo(body0.get(0)); + assertThat(postedDescriptorsSorted.get(1)).isEqualTo(body0.get(1)); + GetSubmodelDescriptorsResult result1 = api.getAllSubmodelDescriptors(2, result0.getPagingMetadata().getCursor()); + List body1 = result1.getResult(); + assertThat(body1).hasSize(2); + assertThat(postedDescriptorsSorted.get(2)).isEqualTo(body1.get(0)); + assertThat(postedDescriptorsSorted.get(3)).isEqualTo(body1.get(1)); + GetSubmodelDescriptorsResult result2 = api.getAllSubmodelDescriptors(2, result1.getPagingMetadata().getCursor()); + List body2 = result2.getResult(); + assertThat(body2).hasSize(1); + assertThat(postedDescriptorsSorted.get(4)).isEqualTo(body2.get(0)); + } + + + @Test + public void whenSendFullObjectStructure_ItemIsProcessedProperly() throws ApiException { + LangStringTextType dType = new LangStringTextType().language(LANG_DE_DE).text("description"); + LangStringNameType nType = new LangStringNameType().language(LANG_DE_DE).text("display"); + ProtocolInformation protInfo = new ProtocolInformation(); + protInfo.addEndpointProtocolVersionItem("23"); + protInfo.addSecurityAttributesItem(new ProtocolInformationSecurityAttributes().key("sec").type(TypeEnum.NONE).value("enabled")); + protInfo.endpointProtocol("https").href("https://reference").subprotocol("sub").subprotocolBody("subBody").subprotocolBodyEncoding("UTF-8"); + Endpoint ep = new Endpoint()._interface("ep_interface").protocolInformation(protInfo); + Reference reference = new Reference().addKeysItem(new Key().type(KeyTypes.FILE).value("./test.yml")).type(ReferenceTypes.EXTERNALREFERENCE); + Extension ext = new Extension().addRefersToItem(reference).addSupplementalSemanticIdsItem(reference).name("ext1").semanticId(reference).value("val"); + DataSpecificationIec61360 dsContent = new DataSpecificationIec61360(); + dsContent.addDefinitionItem(new LangStringDefinitionTypeIec61360().language(LANG_DE_DE).text("def")).addPreferredNameItem(new LangStringPreferredNameTypeIec61360().language(LANG_DE_DE).text("prefName")) + .addShortNameItem(new LangStringShortNameTypeIec61360().language(LANG_DE_DE).text("sn")).dataType(DataTypeIec61360.FILE).levelType(new LevelType().max(true).min(false).nom(false).typ(true)).sourceOfDefinition("sod") + .symbol("$$"); + EmbeddedDataSpecification edSpec = new EmbeddedDataSpecification().dataSpecification(reference).dataSpecificationContent(new DataSpecificationContent(dsContent)); + AdministrativeInformation aInfo = new AdministrativeInformation().addEmbeddedDataSpecificationsItem(edSpec); + SubmodelDescriptor sm = new SubmodelDescriptor().id("sm").id("short").addDescriptionItem(dType).addDisplayNameItem(nType).addEndpointsItem(ep).addExtensionsItem(ext).addSupplementalSemanticIdItem(reference); + sm.setAdministration(aInfo); + SubmodelDescriptor descr = api.postSubmodelDescriptor(sm); + assertThat(descr).isEqualTo(sm); + } + + private void deleteSubmodelDescriptor(String submodelId) throws ApiException { + listener.reset(); + int response = api.deleteSubmodelDescriptorByIdWithHttpInfo(submodelId).getStatusCode(); + //int response = api.deleteSubmodelDescriptorByIdWithHttpInfo(URLEncoder.encode(submodelId, StandardCharsets.UTF_8)).getStatusCode(); + assertThat(response).isEqualTo(NO_CONTENT); + assertThatEventWasSend(RegistryEvent.builder().id(submodelId).type(EventType.SUBMODEL_UNREGISTERED).build()); + } + + private List initialize() throws IOException, InterruptedException, TimeoutException, ApiException { + List descriptors = resourceLoader.loadRepositoryDefinition(SubmodelDescriptor.class); + List repoContent = resourceLoader + .loadRepositoryDefinition(org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor.class); + + for (int i = 0, len = descriptors.size(); i < len; i++) { + SubmodelDescriptor eachDescriptor = descriptors.get(i); + org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor eachEventDescriptor = repoContent.get(i); + ApiResponse response = api.postSubmodelDescriptorWithHttpInfo(eachDescriptor); + assertThat(response.getData()).isEqualTo(eachDescriptor); + assertThat(response.getStatusCode()).isEqualTo(CREATED); + assertThatEventWasSend(RegistryEvent.builder().id(eachDescriptor.getId()).submodelDescriptor(eachEventDescriptor).type(EventType.SUBMODEL_REGISTERED).build()); + } + return descriptors; + } + + private void assertThatEventWasSend(RegistryEvent build) { + RegistryEvent evt = listener.poll(); + assertThat(evt).isEqualTo(build); + } + + public Endpoint defaultClientEndpoint() { + ProtocolInformation protocolInfo = new ProtocolInformation().href("http://127.0.0.1:8099/submodel").endpointProtocol("HTTP").subprotocol("AAS"); + return new Endpoint()._interface("https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003").protocolInformation(protocolInfo); + } + + public org.eclipse.digitaltwin.basyx.submodelregistry.model.Endpoint defaultServerEndpoint() { + org.eclipse.digitaltwin.basyx.submodelregistry.model.ProtocolInformation protocolInfo = new org.eclipse.digitaltwin.basyx.submodelregistry.model.ProtocolInformation("http://127.0.0.1:8099/submodel").endpointProtocol("HTTP").subprotocol("AAS"); + return new org.eclipse.digitaltwin.basyx.submodelregistry.model.Endpoint("https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003", protocolInfo); + } + + + public LangStringTextType description(String description) { + return new LangStringTextType().language(LANG_DE_DE).text(description); + } + + private void assertThrowsApiException(ThrowingRunnable runnable, int statusCode) throws Exception { + try { + runnable.run(); + } catch (ApiException ex) { + assertThat(ex.getCode()).isEqualTo(statusCode); + } + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/logback-test.xml b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/logback-test.xml new file mode 100644 index 000000000..6b59bf5a7 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/logback-test.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/default_repository.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/default_repository.json new file mode 100644 index 000000000..c36618e1b --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/default_repository.json @@ -0,0 +1,8 @@ +[ + { + "id": "identification_1" + }, + { + "id": "identification_2" + } +] diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/whenGetSubmodelDescriptorById_thenOk.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/whenGetSubmodelDescriptorById_thenOk.json new file mode 100644 index 000000000..7e75571fa --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/whenGetSubmodelDescriptorById_thenOk.json @@ -0,0 +1,3 @@ +{ + "id": "identification_2" +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/whenPostSubmodelDescriptorById_thenCreated.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/whenPostSubmodelDescriptorById_thenCreated.json new file mode 100644 index 000000000..423dedf1a --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/whenPostSubmodelDescriptorById_thenCreated.json @@ -0,0 +1,3 @@ +{ + "id": "identification_3" +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/whenPostSubmodelDescriptor_thenApplied.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/whenPostSubmodelDescriptor_thenApplied.json new file mode 100644 index 000000000..196226e92 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/whenPostSubmodelDescriptor_thenApplied.json @@ -0,0 +1,11 @@ +[ + { + "id": "identification_1" + }, + { + "id": "identification_2" + }, + { + "id" : "identification_3" + } +] diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/whenPutSubmodelDescriptor_thenOverridden.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/whenPutSubmodelDescriptor_thenOverridden.json new file mode 100644 index 000000000..c36618e1b --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/whenPutSubmodelDescriptor_thenOverridden.json @@ -0,0 +1,8 @@ +[ + { + "id": "identification_1" + }, + { + "id": "identification_2" + } +] diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/default_repository.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/default_repository.json new file mode 100644 index 000000000..141d4a5e4 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/default_repository.json @@ -0,0 +1,30 @@ +[ + { + "id": "sm1", + "idShort": "1", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8090/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + }, + { + "id": "sm2", + "idShort": "2", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + } +] diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/default_repository.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/default_repository.json new file mode 100644 index 000000000..7fe84b193 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/default_repository.json @@ -0,0 +1,117 @@ +[ + { + "idShort": "identification_7", + "id": "identification_7", + "administration": { + "revision": "22" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_7" + } + ], + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + }, + { + "idShort": "identification_7", + "id": "identification_6", + "administration": { + "revision": "21" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_7" + } + ], + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + }, + { + "idShort": "identification_1", + "id": "identification_1", + "administration": { + "revision": "22" + }, + "description": [ + { + "language": "en-US", + "text": "identification_1" + } + ], + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + }, + { + "idShort": "identification_5", + "id": "identification_5", + "administration": { + "revision": "11" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_5" + } + ], + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + }, + { + "idShort": "identification_2", + "id": "identification_2", + "administration": { + "revision": "2221" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_2" + } + ], + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + } +] diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenMatchSearchBySubmodelDescriptorId_thenGotResult.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenMatchSearchBySubmodelDescriptorId_thenGotResult.json new file mode 100644 index 000000000..ef556d56a --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenMatchSearchBySubmodelDescriptorId_thenGotResult.json @@ -0,0 +1,38 @@ +{ + "idShort": "identification_5", + "id": "identification_5", + "assetType": "tp", + "assetKind": "Type", + "administration": { + "revision": "11" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_5" + }] + , + "submodelDescriptors": [ + { + "idShort": "sm3", + "id": "submodel3", + "description": [ + { + "language": "de-DE", + "text": "G2" + } + ], + + "endpoints" : [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + } + ] +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenRegexSearchBySubmodelDescriptorShortId_thenGotResult.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenRegexSearchBySubmodelDescriptorShortId_thenGotResult.json new file mode 100644 index 000000000..4f77f5fc7 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenRegexSearchBySubmodelDescriptorShortId_thenGotResult.json @@ -0,0 +1,38 @@ +{ + "idShort": "identification_5", + "id": "identification_5", + "assetType": "tp", + "assetKind": "Type", + "administration": { + "revision": "11" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_5" + } + ], + "submodelDescriptors": [ + { + "idShort": "sm3", + "id": "submodel3", + "description": [ + { + "language": "de-DE", + "text": "G2" + } + ], + + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + } + ] +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenRegisterAndUnregisterSubmodel_thenSubmodelIsCreatedAndDeleted_toregister.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenRegisterAndUnregisterSubmodel_thenSubmodelIsCreatedAndDeleted_toregister.json new file mode 100644 index 000000000..431c5b892 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenRegisterAndUnregisterSubmodel_thenSubmodelIsCreatedAndDeleted_toregister.json @@ -0,0 +1,18 @@ +{ + "idShort": "submodel_1", + "id": "submodel1", + "description": [{ + "language": "de-DE", + "text": "Second Sub model descriptor." + }], + + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ]} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenSearchWithSortingByIdNoSortOrder_thenReturnSortedAsc.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenSearchWithSortingByIdNoSortOrder_thenReturnSortedAsc.json new file mode 100644 index 000000000..ef6e17382 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenSearchWithSortingByIdNoSortOrder_thenReturnSortedAsc.json @@ -0,0 +1,140 @@ +[ + { + "idShort": "identification_2", + "id": "identification_2", + "administration": { + "revision": "2221" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_2" + } + ] + }, + { + "idShort": "identification_5", + "id": "identification_5", + "assetType": "tp", + "assetKind": "Type", + "administration": { + "revision": "11" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_5" + } + ], + "submodelDescriptors": [ + { + "idShort": "submodel_0", + "id": "submodel_0", + "description": [ + { + "language": "de-DE", + "text": "G1" + } + ], + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + }, + { + "idShort": "submodel_1", + "id": "submodel1", + "description": [ + { + "language": "de-DE", + "text": "G2" + } + ], + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + }, + { + "idShort": "sm2", + "id": "submodel2", + "description": [ + { + "language": "de-DE", + "text": "G1" + } + ], + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + }, + { + "idShort": "sm3", + "id": "submodel3", + "description": [ + { + "language": "de-DE", + "text": "G2" + } + ], + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + } + ] + }, + { + "idShort": "identification_7", + "id": "identification_6", + "administration": { + "revision": "21" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_7" + } + ] + }, + { + "idShort": "identification_7", + "id": "identification_7", + "assetType": "tp", + "assetKind": "Type", + "administration": { + "revision": "22" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_7" + } + ] + } +] diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenSearchWithSortingByIdShortAsc_thenReturnSortedAsc.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenSearchWithSortingByIdShortAsc_thenReturnSortedAsc.json new file mode 100644 index 000000000..ef6e17382 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenSearchWithSortingByIdShortAsc_thenReturnSortedAsc.json @@ -0,0 +1,140 @@ +[ + { + "idShort": "identification_2", + "id": "identification_2", + "administration": { + "revision": "2221" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_2" + } + ] + }, + { + "idShort": "identification_5", + "id": "identification_5", + "assetType": "tp", + "assetKind": "Type", + "administration": { + "revision": "11" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_5" + } + ], + "submodelDescriptors": [ + { + "idShort": "submodel_0", + "id": "submodel_0", + "description": [ + { + "language": "de-DE", + "text": "G1" + } + ], + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + }, + { + "idShort": "submodel_1", + "id": "submodel1", + "description": [ + { + "language": "de-DE", + "text": "G2" + } + ], + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + }, + { + "idShort": "sm2", + "id": "submodel2", + "description": [ + { + "language": "de-DE", + "text": "G1" + } + ], + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + }, + { + "idShort": "sm3", + "id": "submodel3", + "description": [ + { + "language": "de-DE", + "text": "G2" + } + ], + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + } + ] + }, + { + "idShort": "identification_7", + "id": "identification_6", + "administration": { + "revision": "21" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_7" + } + ] + }, + { + "idShort": "identification_7", + "id": "identification_7", + "assetType": "tp", + "assetKind": "Type", + "administration": { + "revision": "22" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_7" + } + ] + } +] diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenSearchWithSortingByIdShortDesc_thenReturnSortedDesc.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenSearchWithSortingByIdShortDesc_thenReturnSortedDesc.json new file mode 100644 index 000000000..634e5b103 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenSearchWithSortingByIdShortDesc_thenReturnSortedDesc.json @@ -0,0 +1,140 @@ +[ + { + "idShort": "identification_7", + "id": "identification_7", + "assetType": "tp", + "assetKind": "Type", + "administration": { + "revision": "22" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_7" + } + ] + }, + { + "idShort": "identification_7", + "id": "identification_6", + "administration": { + "revision": "21" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_7" + } + ] + }, + { + "idShort": "identification_5", + "id": "identification_5", + "assetType": "tp", + "assetKind": "Type", + "administration": { + "revision": "11" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_5" + } + ], + "submodelDescriptors": [ + { + "idShort": "submodel_0", + "id": "submodel_0", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ], + "description": [ + { + "language": "de-DE", + "text": "G1" + } + ] + }, + { + "idShort": "submodel_1", + "id": "submodel1", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ], + "description": [ + { + "language": "de-DE", + "text": "G2" + } + ] + }, + { + "idShort": "sm2", + "id": "submodel2", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ], + "description": [ + { + "language": "de-DE", + "text": "G1" + } + ] + }, + { + "idShort": "sm3", + "id": "submodel3", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ], + "description": [ + { + "language": "de-DE", + "text": "G2" + } + ] + } + ] + }, + { + "idShort": "identification_2", + "id": "identification_2", + "administration": { + "revision": "2221" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_2" + } + ] + } +] diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenUsePagination_thenUseRefetching.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenUsePagination_thenUseRefetching.json new file mode 100644 index 000000000..fd20087c4 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/integration/whenUsePagination_thenUseRefetching.json @@ -0,0 +1,153 @@ +[ + { + "idShort": "identification_1", + "id": "identification_1", + "administration": { + "revision": "22" + }, + "description": [ + { + "language": "en-US", + "text": "identification_1" + } + ] + }, + { + "idShort": "identification_2", + "id": "identification_2", + "administration": { + "revision": "2221" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_2" + } + ] + }, + { + "idShort": "identification_5", + "id": "identification_5", + "assetType": "tp", + "assetKind": "Type", + "administration": { + "revision": "11" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_5" + } + ], + "submodelDescriptors": [ + { + "idShort": "submodel_0", + "id": "submodel_0", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ], + "description": [ + { + "language": "de-DE", + "text": "G1" + } + ] + }, + { + "idShort": "submodel_1", + "id": "submodel1", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ], + "description": [ + { + "language": "de-DE", + "text": "G2" + } + ] + }, + { + "idShort": "sm2", + "id": "submodel2", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ], + "description": [ + { + "language": "de-DE", + "text": "G1" + } + ] + }, + { + "idShort": "sm3", + "id": "submodel3", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ], + "description": [ + { + "language": "de-DE", + "text": "G2" + } + ] + } + ] + }, + { + "idShort": "identification_7", + "id": "identification_6", + "administration": { + "revision": "21" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_7" + } + ] + }, + { + "idShort": "identification_7", + "id": "identification_7", + "assetType": "tp", + "assetKind": "Type", + "administration": { + "revision": "22" + }, + "description": [ + { + "language": "de-DE", + "text": "identification_7" + } + ] + } +] diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenGetAllSubmodelDescriptorsAndEmptyRepo_thenEmptyList_repo.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenGetAllSubmodelDescriptorsAndEmptyRepo_thenEmptyList_repo.json new file mode 100644 index 000000000..0d4f101c7 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenGetAllSubmodelDescriptorsAndEmptyRepo_thenEmptyList_repo.json @@ -0,0 +1,2 @@ +[ +] diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenGetAllSubmodelDescriptorsOverTwoPages_thenReturnPageStepByStep_repo.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenGetAllSubmodelDescriptorsOverTwoPages_thenReturnPageStepByStep_repo.json new file mode 100644 index 000000000..00ece5a35 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenGetAllSubmodelDescriptorsOverTwoPages_thenReturnPageStepByStep_repo.json @@ -0,0 +1,58 @@ +[ + { + "id": "sm1", + "idShort": "1", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8090/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + }, + { + "id": "sm2", + "idShort": "2", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + }, + { + "id": "sm3", + "idShort": "3", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8080/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + }, + { + "id": "sm4", + "idShort": "4", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8092/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + } +] diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenGetAllSubmodelDescriptors_thenAll.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenGetAllSubmodelDescriptors_thenAll.json new file mode 100644 index 000000000..141d4a5e4 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenGetAllSubmodelDescriptors_thenAll.json @@ -0,0 +1,30 @@ +[ + { + "id": "sm1", + "idShort": "1", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8090/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + }, + { + "id": "sm2", + "idShort": "2", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + } +] diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenGetSubmodelDescritorByIdAndAvailable_thenGotResult.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenGetSubmodelDescritorByIdAndAvailable_thenGotResult.json new file mode 100644 index 000000000..b73443044 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenGetSubmodelDescritorByIdAndAvailable_thenGotResult.json @@ -0,0 +1,14 @@ +{ + "id": "sm2", + "idShort": "2", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptorAndWasAlreadyPresent_thenElementIsOverridden.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptorAndWasAlreadyPresent_thenElementIsOverridden.json new file mode 100644 index 000000000..8d4a406ce --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptorAndWasAlreadyPresent_thenElementIsOverridden.json @@ -0,0 +1,25 @@ +[ + { + "id": "sm1", + "idShort": "1", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8090/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + }, + { + "id": "sm2", + "description": [ + { + "language": "de-DE", + "text": "Overridden" + } + ] + } +] diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptorAndWasAlreadyPresent_thenElementIsOverridden_events.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptorAndWasAlreadyPresent_thenElementIsOverridden_events.json new file mode 100644 index 000000000..73f1a1f11 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptorAndWasAlreadyPresent_thenElementIsOverridden_events.json @@ -0,0 +1,15 @@ +[ + { + "id": "sm2", + "type": "SUBMODEL_REGISTERED", + "submodelDescriptor": { + "id": "sm2", + "description": [ + { + "language": "de-DE", + "text": "Overridden" + } + ] + } + } +] diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptorAndWasNotAlreadyPresent_thenElementIsAdded.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptorAndWasNotAlreadyPresent_thenElementIsAdded.json new file mode 100644 index 000000000..c4137c4bb --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptorAndWasNotAlreadyPresent_thenElementIsAdded.json @@ -0,0 +1,33 @@ +[ + { + "id": "sm1", + "idShort": "1", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8090/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + }, + { + "id": "sm2", + "idShort": "2", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + }, + { + "id": "sm3" + } +] \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptorAndWasNotAlreadyPresent_thenElementIsAdded_events.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptorAndWasNotAlreadyPresent_thenElementIsAdded_events.json new file mode 100644 index 000000000..bde99e74b --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptorAndWasNotAlreadyPresent_thenElementIsAdded_events.json @@ -0,0 +1,9 @@ +[ + { + "id": "sm3", + "type": "SUBMODEL_REGISTERED", + "submodelDescriptor": { + "id": "sm3" + } + } +] \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptor_thenStored.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptor_thenStored.json new file mode 100644 index 000000000..115e75ea2 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptor_thenStored.json @@ -0,0 +1,33 @@ +[ + { + "id": "sm1", + "idShort": "1", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8090/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + }, + { + "id": "sm2", + "idShort": "2", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + }, + { + "id": "new" + } +] \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptor_thenStored_events.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptor_thenStored_events.json new file mode 100644 index 000000000..25975fcc4 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegisterSubmodelDescriptor_thenStored_events.json @@ -0,0 +1,9 @@ +[ + { + "id": "new", + "type": "SUBMODEL_REGISTERED", + "submodelDescriptor": { + "id": "new" + } + } +] diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegistrationUpdateForNewId_AvailableUnderNewIdAndTwoEventsFired.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegistrationUpdateForNewId_AvailableUnderNewIdAndTwoEventsFired.json new file mode 100644 index 000000000..a51fb84c8 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegistrationUpdateForNewId_AvailableUnderNewIdAndTwoEventsFired.json @@ -0,0 +1,30 @@ +[ + { + "id": "sm1", + "idShort": "1", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8090/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + }, + { + "id": "sm3", + "idShort": "2", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + } +] diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegistrationUpdateForNewId_AvailableUnderNewIdAndTwoEventsFired_events.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegistrationUpdateForNewId_AvailableUnderNewIdAndTwoEventsFired_events.json new file mode 100644 index 000000000..c04a3706d --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenRegistrationUpdateForNewId_AvailableUnderNewIdAndTwoEventsFired_events.json @@ -0,0 +1,24 @@ +[ + { + "id": "sm2", + "type": "SUBMODEL_UNREGISTERED" + }, + { + "id": "sm3", + "type": "SUBMODEL_REGISTERED", + "submodelDescriptor": { + "id": "sm3", + "idShort": "2", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8099/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + } + } +] \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenUnregisterSubmodelDescriptorById_thenReturnTrueAndEntryRemoved.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenUnregisterSubmodelDescriptorById_thenReturnTrueAndEntryRemoved.json new file mode 100644 index 000000000..f1e44a7fb --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenUnregisterSubmodelDescriptorById_thenReturnTrueAndEntryRemoved.json @@ -0,0 +1,16 @@ +[ + { + "id": "sm1", + "idShort": "1", + "endpoints": [ + { + "protocolInformation": { + "href": "http://127.0.0.1:8090/submodel", + "endpointProtocol": "HTTP", + "subprotocol": "AAS" + }, + "interface": "https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003" + } + ] + } +] \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenUnregisterSubmodelDescriptorById_thenReturnTrueAndEntryRemoved_events.json b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenUnregisterSubmodelDescriptorById_thenReturnTrueAndEntryRemoved_events.json new file mode 100644 index 000000000..48c13bacb --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-basetests/src/main/resources/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/whenUnregisterSubmodelDescriptorById_thenReturnTrueAndEntryRemoved_events.json @@ -0,0 +1,6 @@ +[ + { + "id": "sm2", + "type": "SUBMODEL_UNREGISTERED" + } +] \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/Readme.md b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/Readme.md new file mode 100644 index 000000000..48e099a1c --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/Readme.md @@ -0,0 +1,20 @@ +# Submodel Registry InMemory Storage + +This registry storage implementation uses in-memory hash maps as document-store and uses the [base java pojos](../basyx.submodelregistry-service-basemodel) as data model. Include this dependency if you want to use this storage implementation: + +```xml + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry + +``` + +Then included, you can active it by either setting the active profile or the *registry.type* attribute: + +``` + -Dspring.profiles.active=logEvents,inMemoryStorage +``` + + + diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/pom.xml new file mode 100644 index 000000000..4cbdbd502 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry + ${revision} + .. + + + basyx.submodelregistry-service-inmemory-storage + jar + + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-basemodel + provided + + + ${project.groupId} + basyx.submodelregistry-service-basetests + test + + + org.projectlombok + lombok + true + + + \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/configuration/InMemorySubmodelStorageConfiguration.java b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/configuration/InMemorySubmodelStorageConfiguration.java new file mode 100644 index 000000000..e03d4bfbd --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/configuration/InMemorySubmodelStorageConfiguration.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.configuration; + +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.CursorEncodingRegistryStorage; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.SubmodelRegistryStorage; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.memory.InMemorySubmodelRegistryStorage; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.memory.ThreadSafeSubmodelRegistryStorageDecorator; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class InMemorySubmodelStorageConfiguration { + + @Bean + @ConditionalOnProperty(prefix = "registry", name = "type", havingValue = "inMemory") + public SubmodelRegistryStorage storage() { + return new ThreadSafeSubmodelRegistryStorageDecorator(new CursorEncodingRegistryStorage(new InMemorySubmodelRegistryStorage())); + } + +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/InMemorySubmodelRegistryStorage.java b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/InMemorySubmodelRegistryStorage.java new file mode 100644 index 000000000..402513019 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/InMemorySubmodelRegistryStorage.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.memory; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; + +import org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.errors.SubmodelAlreadyExistsException; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.errors.SubmodelNotFoundException; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.CursorResult; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.PaginationInfo; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.SubmodelRegistryStorage; + +import lombok.NonNull; + +public class InMemorySubmodelRegistryStorage implements SubmodelRegistryStorage { + + private final HashMap submodelLookupMap = new HashMap<>(); + private final TreeMap sortedSubmodelMap = new TreeMap<>(); + + @Override + public CursorResult getAllSubmodelDescriptors(@NonNull PaginationInfo pRequest) { + PaginationSupport paginationSupport = new PaginationSupport(sortedSubmodelMap); + return paginationSupport.getDescriptorsPaged(pRequest); + } + + @Override + public SubmodelDescriptor getSubmodelDescriptor(@NonNull String submodelId) throws SubmodelNotFoundException { + SubmodelDescriptor elem = submodelLookupMap.get(submodelId); + if (elem == null) { + throw new SubmodelNotFoundException(submodelId); + } + return elem; + } + + @Override + public void insertSubmodelDescriptor(@NonNull SubmodelDescriptor descr) throws SubmodelAlreadyExistsException { + String id = descr.getId(); + SubmodelDescriptor previous = submodelLookupMap.putIfAbsent(id, descr); + if (previous != null) { + throw new SubmodelAlreadyExistsException(id); + } + sortedSubmodelMap.put(id, descr); + } + + @Override + public void removeSubmodelDescriptor(@NonNull String submodelId) throws SubmodelNotFoundException { + SubmodelDescriptor previous = submodelLookupMap.remove(submodelId); + if (previous == null) { + throw new SubmodelNotFoundException(submodelId); + } + sortedSubmodelMap.remove(submodelId); + } + + @Override + public void replaceSubmodelDescriptor(@NonNull String submodelId, @NonNull SubmodelDescriptor descr) throws SubmodelNotFoundException { + if (!submodelLookupMap.containsKey(submodelId)) { + throw new SubmodelNotFoundException(submodelId); + } + String toReplaceId = descr.getId(); + if (!Objects.equals(submodelId, toReplaceId)) { + submodelLookupMap.remove(submodelId); + sortedSubmodelMap.remove(submodelId); + } + submodelLookupMap.put(toReplaceId, descr); + sortedSubmodelMap.put(toReplaceId, descr); + } + + @Override + public Set clear() { + Set keys = new HashSet<>(sortedSubmodelMap.keySet()); + submodelLookupMap.clear(); + sortedSubmodelMap.clear(); + return keys; + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/PaginationSupport.java b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/PaginationSupport.java new file mode 100644 index 000000000..295d0793a --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/PaginationSupport.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.memory; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.CursorResult; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.PaginationInfo; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class PaginationSupport { + + private final TreeMap sortedMap; + + public CursorResult getDescriptorsPaged(PaginationInfo pInfo) { + Map cursorView = getCursorView(pInfo); + Stream> eStream = cursorView.entrySet().stream(); + Stream tStream = eStream.map(Entry::getValue); + tStream = applyLimit(pInfo, tStream); + List descriptorList = tStream.collect(Collectors.toList()); + String cursor = computeNextCursor(descriptorList); + return new CursorResult(cursor, Collections.unmodifiableList(descriptorList)); + } + + private Stream applyLimit(PaginationInfo info, Stream aStream) { + if (info.hasLimit()) { + return aStream.limit(info.getLimit()); + } + return aStream; + } + + private String computeNextCursor(List descriptorList) { + if (!descriptorList.isEmpty()) { + SubmodelDescriptor last = descriptorList.get(descriptorList.size()-1); + String lastId = last.getId(); + return sortedMap.higherKey(lastId); + } + return null; + } + + private Map getCursorView(PaginationInfo info) { + if (info.hasCursor()) { + return sortedMap.tailMap(info.getCursor()); + } else { + return sortedMap; + } + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/ThreadSafeAccess.java b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/ThreadSafeAccess.java new file mode 100644 index 000000000..62c039ee8 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/ThreadSafeAccess.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.memory; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +class ThreadSafeAccess { + + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private final ReadLock readLock = lock.readLock(); + private final WriteLock writeLock = lock.writeLock(); + + public void write(Consumer consumer, A arg1) { + runWithLock(consumer, arg1, readLock); + } + + public void write(BiConsumer consumer, A arg1, B arg2) { + runWithLock(consumer, arg1, arg2, writeLock); + } + + public T read(Function func, A arg1) { + return runWithLock(func, arg1, readLock); + } + + public T write(Supplier supplier) { + return runWithLock(supplier, writeLock); + } + + private T runWithLock(Supplier supplier, Lock lock) { + try { + lock.lock(); + return supplier.get(); + } finally { + lock.unlock(); + } + } + + private void runWithLock(Consumer consumer, A arg1, Lock lock) { + try { + lock.lock(); + consumer.accept(arg1); + } finally { + lock.unlock(); + } + } + + private T runWithLock(Function func, A arg1, Lock lock) { + try { + lock.lock(); + return func.apply(arg1); + } finally { + lock.unlock(); + } + } + + private void runWithLock(BiConsumer consumer, A arg1, B arg2, Lock lock) { + try { + lock.lock(); + consumer.accept(arg1, arg2); + } finally { + lock.unlock(); + } + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/ThreadSafeSubmodelRegistryStorageDecorator.java b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/ThreadSafeSubmodelRegistryStorageDecorator.java new file mode 100644 index 000000000..47df67aa1 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/ThreadSafeSubmodelRegistryStorageDecorator.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.memory; + +import java.util.Set; + +import org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.errors.SubmodelAlreadyExistsException; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.errors.SubmodelNotFoundException; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.CursorResult; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.PaginationInfo; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.SubmodelRegistryStorage; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class ThreadSafeSubmodelRegistryStorageDecorator implements SubmodelRegistryStorage { + + private final SubmodelRegistryStorage storage; + + private final ThreadSafeAccess access = new ThreadSafeAccess(); + + @Override + public CursorResult getAllSubmodelDescriptors(PaginationInfo pRequest) { + return access.read(storage::getAllSubmodelDescriptors, pRequest); + } + + @Override + public Set clear() { + return access.write(storage::clear); + } + + @Override + public SubmodelDescriptor getSubmodelDescriptor( String submodelId) throws SubmodelNotFoundException { + return access.read(storage::getSubmodelDescriptor, submodelId); + } + + @Override + public void insertSubmodelDescriptor(SubmodelDescriptor descr) throws SubmodelAlreadyExistsException { + access.write(storage::insertSubmodelDescriptor, descr); + } + + @Override + public void removeSubmodelDescriptor(String submodelId) throws SubmodelNotFoundException { + access.write(storage::removeSubmodelDescriptor, submodelId); + } + + @Override + public void replaceSubmodelDescriptor(String submodelId, SubmodelDescriptor descr) throws SubmodelNotFoundException { + access.write(storage::replaceSubmodelDescriptor, submodelId, descr); + } + +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/resources/application-inMemoryStorage.yml b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/resources/application-inMemoryStorage.yml new file mode 100644 index 000000000..9bcb2f3db --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/resources/application-inMemoryStorage.yml @@ -0,0 +1,3 @@ +--- +registry: + type: inMemory \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/InMemorySubmodelRegistyStorageTest.java b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/InMemorySubmodelRegistyStorageTest.java new file mode 100644 index 000000000..122bbfef1 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/InMemorySubmodelRegistyStorageTest.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.tests; + +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.memory.InMemorySubmodelRegistryStorage; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.SubmodelRegistryStorage; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; + +@TestPropertySource(properties = { "registry.type=inMemory" }) +@ContextConfiguration(classes = { InMemorySubmodelRegistyStorageTest.class}) +public class InMemorySubmodelRegistyStorageTest extends SubmodelRegistryStorageTest { + + + @Bean + public SubmodelRegistryStorage createBaseStorage() { + return new InMemorySubmodelRegistryStorage(); + } + + +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/BasyxRegistryApiDelegateTest.java b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/BasyxRegistryApiDelegateTest.java new file mode 100644 index 000000000..65be5e670 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/api/BasyxRegistryApiDelegateTest.java @@ -0,0 +1,177 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.tests.api; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertThrows; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.List; + +import org.eclipse.digitaltwin.basyx.submodelregistry.model.GetSubmodelDescriptorsResult; +import org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.api.BasyxSubmodelRegistryApiDelegate; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.api.SubmodelDescriptorsApiController; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.configuration.InMemorySubmodelStorageConfiguration; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.errors.SubmodelNotFoundException; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.events.RegistryEventSink; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.SubmodelRegistryStorage; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.tests.TestResourcesLoader; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = { BasyxSubmodelRegistryApiDelegate.class, SubmodelDescriptorsApiController.class, SubmodelDescriptorsApiController.class, InMemorySubmodelStorageConfiguration.class }) +@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) +@TestPropertySource(properties = { "registry.type=inMemory" }) +public class BasyxRegistryApiDelegateTest { + + private static final String ID_3 = "identification_3"; + + private static final String ID_2_1 = "identification_2.1"; + + private static final String ID_1 = "identification_1"; + + private static final String ID_UNKNOWN = "unknown"; + + private static final String ID_2 = "identification_2"; + + private static final String ID_2_3 = "identification_2.3"; + + @MockBean + private RegistryEventSink listener; + + @Autowired + private SubmodelRegistryStorage storage; + + @Autowired + private SubmodelDescriptorsApiController submodelController; + + + @Rule + public TestResourcesLoader testResourcesLoader = new TestResourcesLoader(); + + @Before + public void initStorage() throws IOException { + testResourcesLoader.loadRepositoryDefinition(SubmodelDescriptor.class).forEach(storage::insertSubmodelDescriptor); + } + + @After + public void clearStorage() { + storage.clear(); + } + + @Test + public void whenDeleteSubmodelDescriptorById_thenNoContent() { + ResponseEntity response = submodelController.deleteSubmodelDescriptorById(encode(ID_1)); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); + } + + @Test + public void whenGetSubmodelDescriptors_thenRepoContent() throws IOException { + List repoContent = testResourcesLoader.loadRepositoryDefinition(SubmodelDescriptor.class); + ResponseEntity response = submodelController.getAllSubmodelDescriptors(null, null); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody().getResult()).asList().containsExactlyInAnyOrderElementsOf(repoContent); + } + + + @Test + public void whenGetSubmodelDescriptorByIdNullArg_thenNullPointer() { + assertThrows(NullPointerException.class, () -> submodelController.getSubmodelDescriptorById(null)); + } + + @Test + public void whenSubmodelDescriptorByIdUnknown_thenNotFound() { + assertThrows(SubmodelNotFoundException.class, () -> submodelController.getSubmodelDescriptorById(encode(ID_UNKNOWN))); + } + + @Test + public void whenGetSubmodelDescriptorById_thenOk() throws IOException { + SubmodelDescriptor expected = testResourcesLoader.load(SubmodelDescriptor.class); + ResponseEntity response = submodelController.getSubmodelDescriptorById(encode(ID_2)); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isEqualTo(expected); + } + + @Test + public void whenPostSubmodelDescriptorById_thenCreated() throws IOException { + SubmodelDescriptor descriptor = testResourcesLoader.load(SubmodelDescriptor.class); + ResponseEntity response = submodelController.postSubmodelDescriptor(descriptor); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED); + + ResponseEntity stored = submodelController.getSubmodelDescriptorById(encode(ID_3)); + assertThat(descriptor).isEqualTo(stored.getBody()); + } + + @Test + public void whenPostSubmodelDescriptor_thenApplied() throws IOException { + SubmodelDescriptor input = new SubmodelDescriptor(ID_3, List.of()); + ResponseEntity response = submodelController.postSubmodelDescriptor(input); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED); + assertThat(response.getBody()).isEqualTo(input); + + ResponseEntity all = submodelController.getAllSubmodelDescriptors(null, null); + List expected = testResourcesLoader.loadList(SubmodelDescriptor.class); + assertThat(all.getBody().getResult()).asList().containsExactlyInAnyOrderElementsOf(expected); + } + + @Test + public void whenPutSubmodelDescriptor_thenOverridden() throws IOException { + SubmodelDescriptor input = new SubmodelDescriptor(ID_2, List.of()); + ResponseEntity response = submodelController.putSubmodelDescriptorById(encode(ID_2), input); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); + + ResponseEntity all = submodelController.getAllSubmodelDescriptors(null, null); + List expected = testResourcesLoader.loadList(SubmodelDescriptor.class); + assertThat(all.getBody().getResult()).asList().containsExactlyInAnyOrderElementsOf(expected); + } + + + @Test + public void whenDeleteAllSubmodelDescritors_thenReturnNoContent() { + ResponseEntity entry = submodelController.deleteAllSubmodelDescriptors(); + assertThat(entry.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); + assertThat(entry.getBody()).isNull(); + } + + private byte[] encode(String id) { + return Base64.getUrlEncoder().encode(id.getBytes(StandardCharsets.UTF_8)); + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/test/resources/logback-test.xml b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/test/resources/logback-test.xml new file mode 100644 index 000000000..6b59bf5a7 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/test/resources/logback-test.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/templates/api.mustache b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/templates/api.mustache new file mode 100644 index 000000000..4502f4d95 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/templates/api.mustache @@ -0,0 +1,186 @@ +/** + * NOTE: This class is auto generated by the swagger code generator program ({{{generatorVersion}}}). + * https://github.com/swagger-api/swagger-codegen + * Do not edit the class manually. + */ +package {{package}}; +{{#imports}}import {{import}}; +{{/imports}} +{{#jdk8-no-delegate}} +import com.fasterxml.jackson.databind.ObjectMapper; +{{/jdk8-no-delegate}} +{{#useOas2}} +import io.swagger.annotations.*; +{{/useOas2}} +{{^useOas2}} +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +{{/useOas2}} +{{#jdk8-no-delegate}} +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +{{/jdk8-no-delegate}} +import org.springframework.http.ResponseEntity; +{{#useBeanValidation}} +import org.springframework.validation.annotation.Validated; +{{/useBeanValidation}} +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.bind.annotation.CookieValue; + +{{#jdk8-no-delegate}} +import javax.servlet.http.HttpServletRequest; +{{/jdk8-no-delegate}} +{{#useBeanValidation}} +import javax.validation.Valid; +import javax.validation.constraints.*; +{{/useBeanValidation}} +{{#jdk8-no-delegate}} +import java.io.IOException; +{{/jdk8-no-delegate}} +import java.util.List; +import java.util.Map; +{{#jdk8-no-delegate}} +import java.util.Optional; +{{/jdk8-no-delegate}} +{{^jdk8-no-delegate}} + {{#useOptional}} +import java.util.Optional; + {{/useOptional}} +{{/jdk8-no-delegate}} +{{#async}} +import java.util.concurrent.{{^isJava8or11}}Callable{{/isJava8or11}}{{#isJava8or11}}CompletableFuture{{/isJava8or11}}; +{{/async}} + +{{>generatedAnnotation}} +{{#useBeanValidation}} +@Validated +{{/useBeanValidation}} +{{#useOas2}} +@Api(value = "{{{baseName}}}", description = "the {{{baseName}}} API") +{{/useOas2}} +{{#operations}} + +public interface {{classname}} { + +{{#isJava8or11}} + + {{^isDelegate}} + Logger log = LoggerFactory.getLogger({{classname}}.class); + + {{#defaultInterfaces}}default {{/defaultInterfaces}}Optional getObjectMapper(){{^defaultInterfaces}};{{/defaultInterfaces}}{{#defaultInterfaces}}{ + return Optional.empty(); + }{{/defaultInterfaces}} + + {{#defaultInterfaces}}default {{/defaultInterfaces}}Optional getRequest(){{^defaultInterfaces}};{{/defaultInterfaces}}{{#defaultInterfaces}}{ + return Optional.empty(); + }{{/defaultInterfaces}} + + {{#defaultInterfaces}}default Optional getAcceptHeader() { + return getRequest().map(r -> r.getHeader("Accept")); + }{{/defaultInterfaces}} + {{/isDelegate}} + {{#isDelegate}} + {{classname}}Delegate getDelegate(); + {{/isDelegate}} +{{/isJava8or11}} +{{#operation}} +{{#contents}} +{{#@first}} + + {{#useOas2}} + @ApiOperation(value = "{{{summary}}}", nickname = "{{{operationId}}}", notes = "{{{notes}}}"{{#returnBaseType}}, response = {{{returnBaseType}}}.class{{/returnBaseType}}{{#returnContainer}}, responseContainer = "{{{returnContainer}}}"{{/returnContainer}}{{#hasAuthMethods}}, authorizations = { + {{#authMethods}}@Authorization(value = "{{name}}"{{#isOAuth}}, scopes = { {{#each scopes}} + @AuthorizationScope(scope = "{{@key}}", description = "{{this}}"){{^@last}},{{/@last}}{{/each}} + }{{/isOAuth}}){{#hasMore}}, + {{/hasMore}}{{/authMethods}} + }{{/hasAuthMethods}}, tags={ {{#vendorExtensions.x-tags}}"{{tag}}",{{/vendorExtensions.x-tags}} }) + @ApiResponses(value = { {{#responses}} + @ApiResponse(code = {{{code}}}, message = "{{{message}}}"{{#baseType}}, response = {{{baseType}}}.class{{/baseType}}{{#containerType}}, responseContainer = "{{{containerType}}}"{{/containerType}}){{#hasMore}},{{/hasMore}}{{/responses}} }) + {{#implicitHeaders}} + @ApiImplicitParams({ + {{#headerParams}} + {{>implicitHeader}} + {{/headerParams}} + }) + {{/implicitHeaders}} + {{/useOas2}} + {{^useOas2}} + @Operation(summary = "{{{summary}}}", description = "{{{notes}}}"{{#hasAuthMethods}}, security = { + {{#authMethods}}@SecurityRequirement(name = "{{name}}"{{#isOAuth}}, scopes = { + {{#each scopes}}"{{@key}}"{{^@last}}, + {{/@last}}{{/each}} + }{{/isOAuth}}){{#hasMore}}, + {{/hasMore}}{{/authMethods}} + }{{/hasAuthMethods}}, tags={ {{#vendorExtensions.x-tags}}"{{tag}}"{{#hasMore}}, {{/hasMore}}{{/vendorExtensions.x-tags}} }) + @ApiResponses(value = { {{#responses}} + @ApiResponse(responseCode = "{{{code}}}", description = "{{{message}}}"{{^vendorExtensions.x-java-is-response-void}}{{#baseType}}, content = @Content({{#schema.extensions.x-content-type}}mediaType = "{{schema.extensions.x-content-type}}", {{/schema.extensions.x-content-type}}{{^containerType}}schema = @Schema(implementation = {{{baseType}}}.class)){{/containerType}}{{#containerType}}array = @ArraySchema(schema = @Schema(implementation = {{{baseType}}}.class))){{/containerType}}{{/baseType}}{{/vendorExtensions.x-java-is-response-void}}){{#hasMore}}, + {{/hasMore}}{{/responses}} }) + {{/useOas2}} + @RequestMapping(value = "{{{path}}}",{{#singleContentTypes}}{{#hasProduces}} + produces = "{{{vendorExtensions.x-accepts}}}", {{/hasProduces}}{{#hasConsumes}} + consumes = "{{{vendorExtensions.x-contentType}}}",{{/hasConsumes}}{{/singleContentTypes}}{{^singleContentTypes}}{{#hasProduces}} + produces = { {{#produces}}"{{{mediaType}}}"{{#hasMore}}, {{/hasMore}}{{/produces}} }, {{/hasProduces}}{{#hasConsumes}} + consumes = { {{#consumes}}"{{{mediaType}}}"{{#hasMore}}, {{/hasMore}}{{/consumes}} }, {{/hasConsumes}}{{/singleContentTypes}} + method = RequestMethod.{{httpMethod}}) + {{#defaultInterfaces}}default {{/defaultInterfaces}}{{#responseWrapper}}{{.}}<{{/responseWrapper}}ResponseEntity<{{>returnTypes}}>{{#responseWrapper}}>{{/responseWrapper}} {{#delegate-method}}_{{/delegate-method}}{{operationId}}({{#parameters}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>cookieParams}}{{>bodyParams}}{{>formParams}}{{#hasMore}}, {{/hasMore}}{{/parameters}}){{^defaultInterfaces}}{{#throwsException}} throws Exception{{/throwsException}};{{/defaultInterfaces}}{{#defaultInterfaces}}{{#throwsException}} throws Exception{{/throwsException}} { + {{#delegate-method}} + return {{operationId}}({{#parameters}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/parameters}}); + } + + // Override this method + default {{#responseWrapper}}{{.}}<{{/responseWrapper}}ResponseEntity<{{>returnTypes}}>{{#responseWrapper}}>{{/responseWrapper}} {{operationId}}({{#parameters}}{{^isBinary}}{{{dataType}}}{{/isBinary}}{{#isBinary}}MultipartFile{{/isBinary}} {{paramName}}{{#hasMore}},{{/hasMore}}{{/parameters}}) { + {{/delegate-method}} + {{^isDelegate}} + if(getObjectMapper().isPresent() && getAcceptHeader().isPresent()) { + {{#examples}} + if (getAcceptHeader().get().contains("{{{contentType}}}")) { + try { + return {{#async}}CompletableFuture.completedFuture({{/async}}new ResponseEntity<>(getObjectMapper().get().readValue("{{#lambdaRemoveLineBreak}}{{#lambdaEscapeDoubleQuote}}{{{example}}}{{/lambdaEscapeDoubleQuote}}{{/lambdaRemoveLineBreak}}", {{>exampleReturnTypes}}.class), HttpStatus.NOT_IMPLEMENTED){{#async}}){{/async}}; + } catch (IOException e) { + log.error("Couldn't serialize response for content type {{{contentType}}}", e); + return {{#async}}CompletableFuture.completedFuture({{/async}}new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR){{#async}}){{/async}}; + } + } + {{/examples}} + } else { + log.warn("ObjectMapper or HttpServletRequest not configured in default {{classname}} interface so no example is generated"); + } + return {{#async}}CompletableFuture.completedFuture({{/async}}new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED){{#async}}){{/async}}; + {{/isDelegate}} + {{#isDelegate}} + {{#parameters}} + {{#@first}} + {{#isPathParam}} + // all path parameters are currently encoded + {{/isPathParam}} + {{/@first}} + {{/parameters}} + {{#parameters}} + {{#isPathParam}} + {{paramName}} = {{paramName}} == null ? null : new String(java.util.Base64.getUrlDecoder().decode({{paramName}}), java.nio.charset.StandardCharsets.UTF_8); + {{/isPathParam}} + {{/parameters}} + return getDelegate().{{operationId}}({{#parameters}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/parameters}}); + {{/isDelegate}} + }{{/defaultInterfaces}} + +{{/@first}} +{{/contents}} +{{/operation}} +} +{{/operations}} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/templates/pojo.mustache b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/templates/pojo.mustache new file mode 100644 index 000000000..4b7cc297c --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/templates/pojo.mustache @@ -0,0 +1,151 @@ +/** + * {{#description}}{{.}}{{/description}}{{^description}}{{classname}}{{/description}} + */{{#description}} +{{#useOas2}}@ApiModel{{/useOas2}}{{^useOas2}}@Schema{{/useOas2}}(description = "{{{description}}}"){{/description}} +{{#useBeanValidation}}@Validated{{/useBeanValidation}} +{{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}} +{{#notNullJacksonAnnotation}}@JsonInclude(JsonInclude.Include.NON_NULL){{/notNullJacksonAnnotation}} +{{#vendorExtensions.x-java-class-annotations}} +{{{.}}} +{{/vendorExtensions.x-java-class-annotations}} +public class {{classname}} {{#parent}}extends {{{parent}}}{{/parent}} {{#serializableModel}}implements Serializable {{#interfaceModels}}, {{classname}}{{^@last}}, {{/@last}}{{#@last}} {{/@last}}{{/interfaceModels}}{{/serializableModel}}{{^serializableModel}}{{#interfaceModels}}{{#@first}}implements {{/@first}}{{classname}}{{^@last}}, {{/@last}}{{#@last}}{{/@last}}{{/interfaceModels}}{{/serializableModel}} { +{{#serializableModel}} + private static final long serialVersionUID = 1L; + +{{/serializableModel}} + {{#vars}} + {{#vendorExtensions.x-java-field-annotations}} + {{{.}}} + {{/vendorExtensions.x-java-field-annotations}} + {{#baseItems this}} + {{#isEnum}} +{{>enumClass}} + {{/isEnum}} + {{/baseItems}} + {{#jackson}} + {{#vendorExtensions.x-is-discriminator-property}} + @JsonTypeId + {{/vendorExtensions.x-is-discriminator-property}} + {{^vendorExtensions.x-is-discriminator-property}} + @JsonProperty("{{baseName}}"){{#withXml}} + @JacksonXmlProperty({{#isXmlAttribute}}isAttribute = true, {{/isXmlAttribute}}{{#xmlNamespace}}namespace="{{xmlNamespace}}", {{/xmlNamespace}}localName = "{{#xmlName}}{{xmlName}}{{/xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}"){{/withXml}} + {{/vendorExtensions.x-is-discriminator-property}} + {{/jackson}} + {{#gson}} + @SerializedName("{{baseName}}") + {{/gson}} + @org.springframework.data.elasticsearch.annotations.Field(name="{{baseName}}"{{#vendorExtensions.x-field-type}}, type=org.springframework.data.elasticsearch.annotations.FieldType.{{.}}{{/vendorExtensions.x-field-type}}{{^vendorExtensions.x-field-type}}{{#isObject}}, type=org.springframework.data.elasticsearch.annotations.FieldType.Object{{/isObject}}{{/vendorExtensions.x-field-type}}) + {{#isContainer}} + {{#useBeanValidation}}@Valid{{/useBeanValidation}} + private {{{datatypeWithEnum}}} {{name}}{{#required}} = {{{defaultValue}}}{{/required}}{{^required}} = null{{/required}}; + {{/isContainer}} + {{^isContainer}} + private {{{datatypeWithEnum}}} {{name}} = {{{defaultValue}}}; + {{/isContainer}} + + {{/vars}} + {{#vars}} + public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { + this.{{name}} = {{name}}; + return this; + } + {{#isListContainer}} + + public {{classname}} add{{nameInCamelCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) { + {{^required}} + if (this.{{name}} == null) { + this.{{name}} = {{{defaultValue}}}; + } + {{/required}} + this.{{name}}.add({{name}}Item); + return this; + } + {{/isListContainer}} + {{#isMapContainer}} + + public {{classname}} put{{nameInCamelCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) { + {{^required}} + if (this.{{name}} == null) { + this.{{name}} = {{{defaultValue}}}; + } + {{/required}} + this.{{name}}.put(key, {{name}}Item); + return this; + } + {{/isMapContainer}} + + /** + {{#description}} + * {{{description}}} + {{/description}} + {{^description}} + * Get {{name}} + {{/description}} + {{#minimum}} + * minimum: {{minimum}} + {{/minimum}} + {{#maximum}} + * maximum: {{maximum}} + {{/maximum}} + * @return {{name}} + **/ + {{#vendorExtensions.extraAnnotation}} + {{{vendorExtensions.extraAnnotation}}} + {{/vendorExtensions.extraAnnotation}} + {{#useOas2}} + @ApiModelProperty({{#example}}example = "{{{example}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}{{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}value = "{{{description}}}") + {{/useOas2}} + {{^useOas2}} + @Schema({{#example}}example = "{{{example}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}{{#isReadOnly}}accessMode = Schema.AccessMode.READ_ONLY, {{/isReadOnly}}description = "{{{description}}}") + {{/useOas2}} + {{#useBeanValidation}}{{>beanValidation}}{{/useBeanValidation}} public {{{datatypeWithEnum}}} {{getter}}() { + return {{name}}; + } + + public void {{setter}}({{{datatypeWithEnum}}} {{name}}) { + this.{{name}} = {{name}}; + } + + {{/vars}} + + @Override + public boolean equals(java.lang.Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + }{{#hasVars}} + {{classname}} {{classVarName}} = ({{classname}}) o; + return {{#vars}}Objects.equals(this.{{name}}, {{classVarName}}.{{name}}){{#hasMore}} && + {{/hasMore}}{{/vars}}{{#parent}} && + super.equals(o){{/parent}};{{/hasVars}}{{^hasVars}} + return true;{{/hasVars}} + } + + @Override + public int hashCode() { + return Objects.hash({{#vars}}{{name}}{{#hasMore}}, {{/hasMore}}{{/vars}}{{#parent}}{{#hasVars}}, {{/hasVars}}super.hashCode(){{/parent}}); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class {{classname}} {\n"); + {{#parent}}sb.append(" ").append(toIndentedString(super.toString())).append("\n");{{/parent}} + {{#vars}}sb.append(" {{name}}: ").append(toIndentedString({{name}})).append("\n"); + {{/vars}}sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(java.lang.Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/Readme.md b/basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/Readme.md new file mode 100644 index 000000000..aa59cc18b --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/Readme.md @@ -0,0 +1,29 @@ +# Submodel Registry Server Kafka Events + +This registry storage implementation uses [Apache Kafka](https://kafka.apache.org/) as event sink. Include this dependency if you want to use this storage implementation: + +```xml + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-kafka-events + +``` + +Then included, you can activate it by either setting the active profile or the "events.sink" property: +``` + -Dspring.profiles.active=kafkaEvents,inMemory +`` + +Dont't forget to also set the kafka bootstrap servers as property: + +``` +-Dspring.kafka.bootstrap-servers=PLAINTEXT://kafka:29092 +``` +Or set the environment variable: +``` +KAFKA_BOOTSTRAP_SERVERS=PLAINTEXT://kafka:29092 +``` + + + diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/pom.xml new file mode 100644 index 000000000..9d56502c8 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry + ${revision} + .. + + + basyx.submodelregistry-service-kafka-events + + jar + + + 2020.0.4 + + + + + org.springframework.cloud + spring-cloud-starter-stream-kafka + + + org.springframework.cloud + spring-cloud-stream + + + org.springframework.cloud + spring-cloud-function-context + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service + + + org.projectlombok + lombok + true + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/KafkaRegistryEventsConfiguration.java b/basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/KafkaRegistryEventsConfiguration.java new file mode 100644 index 000000000..7f7966e21 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/KafkaRegistryEventsConfiguration.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service; + +import org.eclipse.digitaltwin.basyx.submodelregistry.service.events.RegistryEventSink; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.events.kafka.KafkaRegistryEventSink; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConditionalOnProperty(prefix = "events", name = "sink", havingValue = "kafka") +public class KafkaRegistryEventsConfiguration { + + @Bean + public RegistryEventSink eventSink() { + return new KafkaRegistryEventSink(); + } + +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/events/kafka/KafkaRegistryEventSink.java b/basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/events/kafka/KafkaRegistryEventSink.java new file mode 100644 index 000000000..3374a6212 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/events/kafka/KafkaRegistryEventSink.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.events.kafka; + +import org.eclipse.digitaltwin.basyx.submodelregistry.service.events.RegistryEvent; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.events.RegistryEventSink; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.stream.function.StreamBridge; + +import lombok.extern.log4j.Log4j2; + +@Log4j2 +public class KafkaRegistryEventSink implements RegistryEventSink { + + private static final String REGISTRY_BINDING_NAME = "registryBinding"; + + @Autowired + private StreamBridge streamBridge; + + @Override + public void consumeEvent(RegistryEvent evt) { + boolean msgSent = streamBridge.send(REGISTRY_BINDING_NAME, evt); + if (msgSent) { + log.info("Registration event message sent to stream."); + } else { + log.error("Failed to sent registration event info."); + } + } + +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/events/kafka/LogConsumer.java b/basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/events/kafka/LogConsumer.java new file mode 100644 index 000000000..3f66177a2 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/events/kafka/LogConsumer.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.events.kafka; + +import java.io.IOException; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Component; + +import lombok.extern.log4j.Log4j2; + +@Component +@Log4j2 +@ConditionalOnProperty(prefix = "events", name = "sink", havingValue = "kafka") +public class LogConsumer { + + + @KafkaListener(topics = "submodel-registry", groupId = "log", autoStartup = "true") + public void consume(String message) throws IOException { + log.debug("Kafka Event received -> " + message); + } + +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/src/main/resources/application-kafkaEvents.yml b/basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/src/main/resources/application-kafkaEvents.yml new file mode 100644 index 000000000..f3f1ba97d --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-kafka-events/src/main/resources/application-kafkaEvents.yml @@ -0,0 +1,6 @@ +--- +events: + sink: kafka +spring: + kafka: + bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/Readme.md b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/Readme.md new file mode 100644 index 000000000..3fedfa7b9 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/Readme.md @@ -0,0 +1,29 @@ +# Basyx Submodel Registry Server MongoDb Storage + +This registry storage implementation uses [MongoDb](https://www.mongodb.com/) as document-store and generates a specific data model with MongoDb annotations. Include this dependency if you want to use this storage implementation: + +```xml + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-mongodb-storage + +``` + +Then included, you can activate it by either setting the active profile or the *registry.type* attribute: + +``` + -Dspring.profiles.active=logEvents,mongoDbStorage +``` + +Dont't forget to also set the mongodb url as property + +``` +-Dspring.data.mongodb.uri=mongodb://admin:admin@localhost:27017/ +``` + +or use the environment variable + +``` +SPRING_DATA_MONGODB_URI=mongodb://admin:admin@localhost:27017/ +``` + diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/open-api/patch-mongodb-annotations.yaml b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/open-api/patch-mongodb-annotations.yaml new file mode 100644 index 000000000..771f15c1a --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/open-api/patch-mongodb-annotations.yaml @@ -0,0 +1,12 @@ +- op: add + path: /components/schemas/SubmodelDescriptor/x-class-extra-annotation + value: '@org.springframework.data.mongodb.core.mapping.Document(collection = "submodeldescriptors")' +- op: add + path: /components/schemas/SubmodelDescriptor/allOf/1/properties/id/x-field-extra-annotation + value: '@org.springframework.data.annotation.Id' +- op: add + path: /components/schemas/Endpoint/properties/interface/x-field-extra-annotation + value: '@org.springframework.data.mongodb.core.mapping.Field(name="interface")@JsonProperty("interface")' +- op: add + path: /components/schemas/PagedResult_paging_metadata/x-field-extra-annotation + value: '@org.springframework.data.mongodb.core.mapping.Field(name="paging_metadata")@JsonProperty("paging_metadata")' diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/pom.xml new file mode 100644 index 000000000..6ee806564 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/pom.xml @@ -0,0 +1,231 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry + ${revision} + .. + + + basyx.submodelregistry-service-mongodb-storage + jar + + + 2020.0.4 + ${project.basedir}/${openapi.folder.name} + ${openapi.folder}/${openapi.mongodb.file.name} + ${openapi.folder}/temporary-extensions-result-file.yaml + ${openapi.folder}/${openapi.name} + patch-mongodb-annotations.yaml + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + generate-sources + + add-source + + + + ${generated.folder}/java + + + + + + + maven-clean-plugin + + + + ${project.basedir}/${generated.folder} + + **/.gitkeep + + false + + + ${openapi.folder} + + .gitkeep + ${patch.mongo-extensions.name} + + false + + + + + + + de.dfki.cos.basys.common + jsonpatch-maven-plugin + + + base-extensions + generate-sources + + jsonpatch-maven-plugin + + + ${project.basedir}/../${openapi.folder.name}/${openapi.base.name} + ${project.basedir}/../${openapi.folder.name}/${patch.base-extensions.name} + ${openapi.extensions.result.file} + + + + mongodb-annotation-extensions + generate-sources + + jsonpatch-maven-plugin + + + ${openapi.extensions.result.file} + ${project.basedir}/${openapi.folder.name}/${patch.mongo-extensions.name} + ${openapi.result.file} + + + + + + org.openapitools + openapi-generator-maven-plugin + + + + generate + + + true + false + false + + ${openapi.result.file} + spring + spring-boot + ${project.basedir}/${generated.folder} + + + + false + true + java8 + java + true + true + true + org.eclipse.digitaltwin.basyx.submodelregistry.service.configuration + org.eclipse.digitaltwin.basyx.submodelregistry.service.api + org.eclipse.digitaltwin.basyx.submodelregistry.service + org.eclipse.digitaltwin.basyx.submodelregistry.model + true + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + true + + + + + + + + + org.testcontainers + mongodb + test + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + org.springframework.cloud + spring-cloud-function-context + + + + javax.validation + validation-api + + + org.springframework.boot + spring-boot-starter-validation + + + javax.xml.bind + jaxb-api + + + com.google.guava + guava + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-basetests + test + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.apache.commons + commons-lang3 + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.hamcrest + hamcrest-core + + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/configuration/MongoDbConfiguration.java b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/configuration/MongoDbConfiguration.java new file mode 100644 index 000000000..eb5dfea36 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/configuration/MongoDbConfiguration.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.configuration; + +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.CursorEncodingRegistryStorage; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.SubmodelRegistryStorage; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.mongodb.MongoDbSubmodelRegistryStorage; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.MongoDatabaseFactory; +import org.springframework.data.mongodb.MongoTransactionManager; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.scheduling.annotation.EnableAsync; + +@Configuration +@ConditionalOnProperty(prefix = "registry", name = "type", havingValue = "mongodb") +@EnableAsync +public class MongoDbConfiguration { + + @Bean + public SubmodelRegistryStorage createStorage(MongoTemplate template) { + return new CursorEncodingRegistryStorage(new MongoDbSubmodelRegistryStorage(template)); + } + + @Bean + public MongoTransactionManager transactionManager(MongoDatabaseFactory dbFactory) { + return new MongoTransactionManager(dbFactory); + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/mongodb/MongoDbSubmodelRegistryStorage.java b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/mongodb/MongoDbSubmodelRegistryStorage.java new file mode 100644 index 000000000..c79187730 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/mongodb/MongoDbSubmodelRegistryStorage.java @@ -0,0 +1,158 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.mongodb; + +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.errors.SubmodelAlreadyExistsException; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.errors.SubmodelNotFoundException; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.CursorResult; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.PaginationInfo; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.SubmodelRegistryStorage; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.SessionScoped; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.aggregation.SortOperation; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; + +import com.mongodb.ClientSessionOptions; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class MongoDbSubmodelRegistryStorage implements SubmodelRegistryStorage { + + // mongodb maps all id fields internally to _id + private static final String ID = "_id"; + + private final MongoTemplate template; + + @Override + public CursorResult getAllSubmodelDescriptors(@NonNull PaginationInfo pRequest) { + List allAggregations = new LinkedList<>(); + applySorting(allAggregations); + applyPagination(pRequest, allAggregations); + AggregationResults results = template.aggregate(Aggregation.newAggregation(allAggregations), SubmodelDescriptor.class, SubmodelDescriptor.class); + List foundDescriptors = results.getMappedResults(); + String cursor = resolveCursor(pRequest, foundDescriptors); + return new CursorResult(cursor, foundDescriptors); + } + + @Override + public Set clear() { + Query query = Query.query(Criteria.where(ID).exists(true)); + query.fields().include(ID); + List list = template.findAllAndRemove(query, SubmodelDescriptor.class); + return list.stream().map(SubmodelDescriptor::getId).collect(Collectors.toSet()); + } + + @Override + public SubmodelDescriptor getSubmodelDescriptor(@NonNull String submodelId) throws SubmodelNotFoundException { + SubmodelDescriptor descriptor = template.findById(submodelId, SubmodelDescriptor.class); + if (descriptor == null) { + throw new SubmodelNotFoundException(submodelId); + } + return descriptor; + } + + @Override + public void insertSubmodelDescriptor(@NonNull SubmodelDescriptor descr) throws SubmodelAlreadyExistsException { + try { + template.insert(descr); + } catch (DuplicateKeyException ex) { + throw new SubmodelAlreadyExistsException(descr.getId()); + } + } + + @Override + public void replaceSubmodelDescriptor(@NonNull String submodelId, @NonNull SubmodelDescriptor descr) throws SubmodelNotFoundException { + if (!Objects.equals(submodelId, descr.getId())) { + // we can not update the _id element -> delete and save + moveInTransaction(submodelId, descr); + } else { + Query query = Query.query(Criteria.where(ID).is(submodelId)); + SubmodelDescriptor replaced = template.findAndReplace(query, descr); + if (replaced == null) { + throw new SubmodelNotFoundException(submodelId); + } + } + } + + @Override + public void removeSubmodelDescriptor(@NonNull String submodelId) throws SubmodelNotFoundException { + Query query = Query.query(Criteria.where(ID).is(submodelId)); + if (template.remove(query, SubmodelDescriptor.class).getDeletedCount() == 0) { + throw new SubmodelNotFoundException(submodelId); + } + } + + private void moveInTransaction(String submodelId, SubmodelDescriptor descriptor) { + SessionScoped scoped = template.withSession(ClientSessionOptions.builder().build()); + boolean removed = scoped.execute(operations -> { + Query query = Query.query(Criteria.where(ID).is(submodelId)); + if (operations.remove(query, SubmodelDescriptor.class).getDeletedCount() == 0) { + return false; + } + operations.save(descriptor); + return true; + }); + if (!removed) { + throw new SubmodelNotFoundException(submodelId); + } + } + + private void applySorting(List allAggregations) { + SortOperation sortOp = Aggregation.sort(Direction.ASC, ID); + allAggregations.add(sortOp); + } + + private String resolveCursor(PaginationInfo pRequest, List foundDescriptors) { + if (foundDescriptors.isEmpty() || !pRequest.isPaged()) { + return null; + } + SubmodelDescriptor last = foundDescriptors.get(foundDescriptors.size() - 1); + return last.getId(); + } + + private void applyPagination(PaginationInfo pRequest, List allAggregations) { + if (pRequest.getCursor() != null) { + allAggregations.add(Aggregation.match(Criteria.where(ID).gt(pRequest.getCursor()))); + } + if (pRequest.getLimit() != null) { + allAggregations.add(Aggregation.limit(pRequest.getLimit())); + } + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/main/resources/application-mongoDbStorage.yml b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/main/resources/application-mongoDbStorage.yml new file mode 100644 index 000000000..2f313214d --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/main/resources/application-mongoDbStorage.yml @@ -0,0 +1,6 @@ +registry: + type: mongodb +spring: + data: + mongodb: + database: submodelregistry diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/MongoDbSubmodelRegistryStorageTest.java b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/MongoDbSubmodelRegistryStorageTest.java new file mode 100644 index 000000000..ceeb668f6 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/MongoDbSubmodelRegistryStorageTest.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.tests; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.ExecutionException; + +import org.bson.Document; +import org.junit.ClassRule; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.TestPropertySource; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.utility.DockerImageName; + +import com.mongodb.ExplainVerbosity; +import com.mongodb.client.MongoCollection; + +@TestPropertySource(properties = { "registry.type=mongodb", "spring.data.mongodb.database=submodelregistry" }) +@ContextConfiguration(classes = { org.eclipse.digitaltwin.basyx.submodelregistry.service.configuration.MongoDbConfiguration.class }) +@EnableAutoConfiguration +public class MongoDbSubmodelRegistryStorageTest extends SubmodelRegistryStorageTest { + + @Value("${spring.data.mongodb.database}") + private static String DATABASE_NAME; + + @ClassRule + public static final MongoDBContainer MONGODB_CONTAINER = new MongoDBContainer(DockerImageName.parse("mongo:5.0.10")); + + @Autowired + private MongoTemplate template; + + @DynamicPropertySource + static void assignAdditionalProperties(DynamicPropertyRegistry registry) throws InterruptedException, ExecutionException { + String uri = MONGODB_CONTAINER.getConnectionString() + "/" + DATABASE_NAME; + registry.add("spring.data.mongodb.uri", () -> uri); + } + + @Test + public void whenGetById_NotAllDocumentsScannedButIndexUsed() { + MongoCollection collection = template.getCollection("submodeldescriptors"); + Document doc = collection.find(new Document("_id", "11")).explain(ExplainVerbosity.QUERY_PLANNER); + assertThat(doc.toJson()).doesNotContain("\"COLLSCAN\""); + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/logback-test.xml b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/logback-test.xml new file mode 100644 index 000000000..6b59bf5a7 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/logback-test.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testAdministrationCreatorKeysType.json b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testAdministrationCreatorKeysType.json new file mode 100644 index 000000000..65b642955 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testAdministrationCreatorKeysType.json @@ -0,0 +1,7 @@ +{ + "administration.creator.keys": { + "$elemMatch": { + "type": "key1" + } + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testAndQuerySubmodelFunctional.json b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testAndQuerySubmodelFunctional.json new file mode 100644 index 000000000..36a37131a --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testAndQuerySubmodelFunctional.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testAssetKindEnum.json b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testAssetKindEnum.json new file mode 100644 index 000000000..236005c20 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testAssetKindEnum.json @@ -0,0 +1,3 @@ +{ + "assetKind": "Instance" +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testEndpointsProtocolInformationEndpointProtocolVersion.json b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testEndpointsProtocolInformationEndpointProtocolVersion.json new file mode 100644 index 000000000..d57e0aa8a --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testEndpointsProtocolInformationEndpointProtocolVersion.json @@ -0,0 +1,11 @@ +{ + "endpoints": { + "$elemMatch": { + "protocolInformation.endpointProtocolVersion": { + "$in": [ + "1" + ] + } + } + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testEndpointsProtocolInformationEndpointProtocolVersionRegex.json b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testEndpointsProtocolInformationEndpointProtocolVersionRegex.json new file mode 100644 index 000000000..481591528 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testEndpointsProtocolInformationEndpointProtocolVersionRegex.json @@ -0,0 +1,16 @@ +{ + "endpoints": { + "$elemMatch": { + "protocolInformation.endpointProtocolVersion": { + "$in": [ + { + "$regularExpression": { + "pattern": "a.*c", + "options": "" + } + } + ] + } + } + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testId.json b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testId.json new file mode 100644 index 000000000..4798d4172 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testId.json @@ -0,0 +1,3 @@ +{ + "_id": "myId" +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testIdShort.json b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testIdShort.json new file mode 100644 index 000000000..d5ab0a56a --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testIdShort.json @@ -0,0 +1,3 @@ +{ + "idShort": "myIdShort" +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testMatchSubmodelFunctionalProjection.json b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testMatchSubmodelFunctionalProjection.json new file mode 100644 index 000000000..2ebac1152 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testMatchSubmodelFunctionalProjection.json @@ -0,0 +1,31 @@ +{ + "$filter": { + "input": "$submodelDescriptors", + "as": "a", + "cond": { + "$ne": [ + { + "$size": { + "$ifNull": [ + { + "$filter": { + "input": "$$a.endpoints", + "as": "b", + "cond": { + "$eq": [ + "$$b.protocolInformation.endpointProtocol", + "2.0.0" + ] + } + } + }, + [ + ] + ] + } + }, + 0 + ] + } + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testMatchSubmodelListProjection.json b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testMatchSubmodelListProjection.json new file mode 100644 index 000000000..43d8cb183 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testMatchSubmodelListProjection.json @@ -0,0 +1,50 @@ +{ + "$filter": { + "input": "$submodelDescriptors", + "as": "a", + "cond": { + "$ne": [ + { + "$size": { + "$ifNull": [ + { + "$filter": { + "input": "$$a.endpoints", + "as": "b", + "cond": { + "$ne": [ + { + "$size": { + "$ifNull": [ + { + "$filter": { + "input": "$$b.protocolInformation.endpointProtocolVersion", + "as": "c", + "cond": { + "$eq": [ + "$$c", + "2.0.0" + ] + } + } + }, + [ + ] + ] + } + }, + 0 + ] + } + } + }, + [ + ] + ] + } + }, + 0 + ] + } + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testMultipleShellExtensions.json b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testMultipleShellExtensions.json new file mode 100644 index 000000000..26f8c3026 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testMultipleShellExtensions.json @@ -0,0 +1,32 @@ +{ + "$and": [ + { + "extensions": { + "$elemMatch": { + "$and": [ + { + "value": "RED" + }, + { + "name": "COLOR" + } + ] + } + } + }, + { + "extensions": { + "$elemMatch": { + "$and": [ + { + "value": "BLUE" + }, + { + "name": "COLOR" + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testRegexSubmodelFunctionalProjection.json b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testRegexSubmodelFunctionalProjection.json new file mode 100644 index 000000000..ef10d8f19 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testRegexSubmodelFunctionalProjection.json @@ -0,0 +1,31 @@ +{ + "$filter": { + "input": "$submodelDescriptors", + "as": "a", + "cond": { + "$ne": [ + { + "$size": { + "$ifNull": [ + { + "$filter": { + "input": "$$a.endpoints", + "as": "b", + "cond": { + "$regexMatch": { + "input": "$$b.protocolInformation.endpointProtocol", + "regex": "^a_.*$" + } + } + } + }, + [ + ] + ] + } + }, + 0 + ] + } + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testRegexSubmodelListProjection.json b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testRegexSubmodelListProjection.json new file mode 100644 index 000000000..dea66a803 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testRegexSubmodelListProjection.json @@ -0,0 +1,50 @@ +{ + "$filter": { + "input": "$submodelDescriptors", + "as": "a", + "cond": { + "$ne": [ + { + "$size": { + "$ifNull": [ + { + "$filter": { + "input": "$$a.endpoints", + "as": "b", + "cond": { + "$ne": [ + { + "$size": { + "$ifNull": [ + { + "$filter": { + "input": "$$b.protocolInformation.endpointProtocolVersion", + "as": "c", + "cond": { + "$regexMatch": { + "input": "$$c", + "regex": "^a_.*$" + } + } + } + }, + [ + ] + ] + } + }, + 0 + ] + } + } + }, + [ + ] + ] + } + }, + 0 + ] + } + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testShellExtensionName.json b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testShellExtensionName.json new file mode 100644 index 000000000..3e2bdec06 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testShellExtensionName.json @@ -0,0 +1,14 @@ +{ + "extensions": { + "$elemMatch": { + "$and": [ + { + "value": "RED" + }, + { + "name": "COLOR" + } + ] + } + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testSmEndpointsProtocolInformationSecurityAttributesValue.json b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testSmEndpointsProtocolInformationSecurityAttributesValue.json new file mode 100644 index 000000000..0f95ade2d --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testSmEndpointsProtocolInformationSecurityAttributesValue.json @@ -0,0 +1,15 @@ +{ + "submodelDescriptors": { + "$elemMatch": { + "endpoints": { + "$elemMatch": { + "protocolInformation.securityAttributes": { + "$elemMatch": { + "value": "attrValue" + } + } + } + } + } + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testSubmodelId.json b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testSubmodelId.json new file mode 100644 index 000000000..0f1798116 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testSubmodelId.json @@ -0,0 +1,7 @@ +{ + "submodelDescriptors": { + "$elemMatch": { + "_id": "myId" + } + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testSubmodelIdShort.json b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testSubmodelIdShort.json new file mode 100644 index 000000000..3f99478a9 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-mongodb-storage/src/test/resources/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/testSubmodelIdShort.json @@ -0,0 +1,7 @@ +{ + "submodelDescriptors": { + "$elemMatch": { + "idShort": "myId" + } + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/Readme.md b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/Readme.md new file mode 100644 index 000000000..08b5e0726 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/Readme.md @@ -0,0 +1,9 @@ +# Basyx Submodel Registry Service Release Kafka Mem + +This project creates a docker image based on the specific spring-boot jar file. + +To reduce dependencies and jar file size, we want to produce a docker file for each combination of registry-event-sink and storage combination. + +We use Kafka events and in-memory storage in the docker image created here. + +To test it on your local PC, invoke the build-image script and try the [docker compose file in the sibling project](../docker-compose/docker-compose.yml). \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/pom.xml new file mode 100644 index 000000000..5a80d3c5d --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry + ${revision} + .. + + + basyx.submodelregistry-service-release-kafka-mem + + + 2020.0.4 + org.eclipse.digitaltwin.basyx.submodelregistry.service.OpenApiGeneratorApplication + submodel-registry-kafka-mem + + + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-basemodel + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-kafka-events + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-inmemory-storage + + + org.apache.commons + commons-lang3 + + + org.junit.vintage + junit-vintage-engine + test + + + org.hamcrest + hamcrest-core + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-basetests + test + + + org.testcontainers + kafka + ${testcontainers.version} + test + + + \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/src/main/docker/Dockerfile b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/src/main/docker/Dockerfile new file mode 100644 index 000000000..10d2426fc --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/src/main/docker/Dockerfile @@ -0,0 +1,16 @@ +FROM openjdk:11-jre-slim as builder +COPY maven/${project.build.finalName}.jar ./ +RUN java -Djarmode=layertools -jar ${project.build.finalName}.jar extract + +FROM openjdk:11-jre-slim +RUN mkdir /workspace +WORKDIR /workspace +COPY --from=builder dependencies/ ./ +COPY --from=builder snapshot-dependencies/ ./ +RUN true +COPY --from=builder spring-boot-loader/ ./ +COPY --from=builder application/ ./ +EXPOSE 8080 +ENV SPRING_PROFILES_ACTIVE=kafkaEvents,inMemoryStorage +ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/.urandom", "org.springframework.boot.loader.JarLauncher"] + diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/src/main/resources/.empty b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/src/main/resources/.empty new file mode 100644 index 000000000..35761803e --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/src/main/resources/.empty @@ -0,0 +1 @@ +Just an empty file so that the project is build \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/KafkaEventsInMemoryStorageIntegrationTest.java b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/KafkaEventsInMemoryStorageIntegrationTest.java new file mode 100644 index 000000000..8d035d9bf --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/KafkaEventsInMemoryStorageIntegrationTest.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.memory; + +import java.time.Duration; + +import org.eclipse.digitaltwin.basyx.submodelregistry.service.tests.integration.BaseEventListener; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.tests.integration.BaseIntegrationTest; +import org.junit.ClassRule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Component; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.TestPropertySource; +import org.testcontainers.containers.KafkaContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.utility.DockerImageName; + +@TestPropertySource(properties = { "registry.type=inMemory", "events.sink=kafka" }) + +public class KafkaEventsInMemoryStorageIntegrationTest extends BaseIntegrationTest { + + private static Logger logger = LoggerFactory.getLogger("KAFKA"); + private static Slf4jLogConsumer logConsumer = new Slf4jLogConsumer(logger); + + @ClassRule + public static KafkaContainer KAFKA = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:6.2.1")).withStartupTimeout(Duration.ofSeconds(180)); + + + @DynamicPropertySource + static void assignAdditionalProperties(DynamicPropertyRegistry registry) { + KAFKA.followOutput(logConsumer); + logger.info("Connecting to KAFKA on: " + KAFKA.getBootstrapServers()); + registry.add("spring.kafka.bootstrap-servers", KAFKA::getBootstrapServers); + + } + + @Component + public static class KafkaEventListener extends BaseEventListener { + + @KafkaListener(topics = "submodel-registry", groupId = "test") + public void receive(String message) { + super.offer(message); + } + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/src/test/resources/logback-test.xml b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/src/test/resources/logback-test.xml new file mode 100644 index 000000000..6b59bf5a7 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/src/test/resources/logback-test.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/Readme.md b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/Readme.md new file mode 100644 index 000000000..8c2ee29f8 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/Readme.md @@ -0,0 +1,9 @@ +# Basyx Submodel Registry Service Release Kafka MongoDb + +This project creates a docker image based on the specific spring-boot jar file. + +To reduce dependencies and jar file size, we want to produce a docker file for each combination of registry-event-sink and storage combination. + +We use Kafka events and an MongoDb storage in the docker image created here. + +To test it on your local PC, invoke the build-image script and try the [docker compose file in the sibling project](../docker-compose/docker-compose.yml). \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/pom.xml new file mode 100644 index 000000000..54be7aa54 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry + ${revision} + .. + + basyx.submodelregistry-service-release-kafka-mongodb + + 2020.0.4 + org.eclipse.digitaltwin.basyx.submodelregistry.service.OpenApiGeneratorApplication + submodel-registry-kafka-mongodb + + + + + org.testcontainers + mongodb + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-mongodb-storage + + + org.apache.commons + commons-lang3 + + + org.junit.vintage + junit-vintage-engine + test + + + org.hamcrest + hamcrest-core + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-kafka-events + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-basetests + test + + + org.testcontainers + kafka + ${testcontainers.version} + test + + + \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/src/main/docker/Dockerfile b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/src/main/docker/Dockerfile new file mode 100644 index 000000000..f074db10f --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/src/main/docker/Dockerfile @@ -0,0 +1,16 @@ +FROM openjdk:11-jre-slim as builder +COPY maven/${project.build.finalName}.jar ./ +RUN java -Djarmode=layertools -jar ${project.build.finalName}.jar extract + +FROM openjdk:11-jre-slim +RUN mkdir /workspace +WORKDIR /workspace +COPY --from=builder dependencies/ ./ +COPY --from=builder snapshot-dependencies/ ./ +RUN true +COPY --from=builder spring-boot-loader/ ./ +COPY --from=builder application/ ./ +EXPOSE 8080 +ENV SPRING_PROFILES_ACTIVE=kafkaEvents,mongoDbStorage +ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/.urandom", "org.springframework.boot.loader.JarLauncher"] + diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/src/main/resources/.empty b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/src/main/resources/.empty new file mode 100644 index 000000000..35761803e --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/src/main/resources/.empty @@ -0,0 +1 @@ +Just an empty file so that the project is build \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/mongodb/KafkaEventsMongoDbStorageIntegrationTest.java b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/mongodb/KafkaEventsMongoDbStorageIntegrationTest.java new file mode 100644 index 000000000..382b42ecb --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/mongodb/KafkaEventsMongoDbStorageIntegrationTest.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.mongodb; + +import java.time.Duration; +import java.util.stream.Stream; + +import org.eclipse.digitaltwin.basyx.submodelregistry.service.tests.integration.BaseEventListener; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.tests.integration.BaseIntegrationTest; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Component; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.TestPropertySource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.KafkaContainer; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.utility.DockerImageName; + +@TestPropertySource(properties = { "registry.type=mongodb", "events.sink=kafka", "spring.data.mongodb.database=submodel-registry" }) +public class KafkaEventsMongoDbStorageIntegrationTest extends BaseIntegrationTest { + + private static Logger logger = LoggerFactory.getLogger("KAFKA"); + private static Slf4jLogConsumer logConsumer = new Slf4jLogConsumer(logger); + + @Value("${spring.data.mongodb.database}") + private static String DATABASE_NAME; + + public static KafkaContainer KAFKA = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:6.2.1")).withStartupTimeout(Duration.ofSeconds(120));; + + public static final MongoDBContainer MONGODB_CONTAINER = new MongoDBContainer(DockerImageName.parse("mongo:5.0.10")); + + @DynamicPropertySource + static void assignAdditionalProperties(DynamicPropertyRegistry registry) { + KAFKA.followOutput(logConsumer); + logger.info("Connecting to KAFKA on: " + KAFKA.getBootstrapServers()); + String uri = MONGODB_CONTAINER.getConnectionString() + "/" + DATABASE_NAME; + registry.add("spring.data.mongodb.uri", () -> uri); + registry.add("spring.kafka.bootstrap-servers", KAFKA::getBootstrapServers); + } + + @BeforeClass + public static void startContainersInParallel() { + Stream.of(KAFKA, MONGODB_CONTAINER).parallel().forEach(GenericContainer::start); + } + + @AfterClass + public static void stopContainersInParallel() { + Stream.of(KAFKA, MONGODB_CONTAINER).parallel().forEach(GenericContainer::stop); + } + + @Component + public static class KafkaEventListener extends BaseEventListener { + + @KafkaListener(topics = "submodel-registry", groupId = "test") + public void receive(String message) { + super.offer(message); + } + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/src/test/resources/logback-test.xml b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/src/test/resources/logback-test.xml new file mode 100644 index 000000000..6b59bf5a7 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/src/test/resources/logback-test.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/Readme.md b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/Readme.md new file mode 100644 index 000000000..87c0f28aa --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/Readme.md @@ -0,0 +1,9 @@ +# Basyx Submodel Registry Service Release Log Mem + +This project creates a docker image based on the specific spring-boot jar file. + +To reduce dependencies and jar file size, we want to produce a docker file for each combination of registry-event-sink and storage combination. + +We log registry events and use an in-memory storage in the docker image created here. + +To test it on your local PC, invoke the build-image script and try the [docker compose file in the sibling project](../docker-compose/docker-compose.yml). \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/pom.xml new file mode 100644 index 000000000..717218aac --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry + ${revision} + .. + + + basyx.submodelregistry-service-release-log-mem + + + 2020.0.4 + org.eclipse.digitaltwin.basyx.submodelregistry.service.OpenApiGeneratorApplication + submodel-registry-log-mem + + + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-basemodel + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-inmemory-storage + + + org.junit.vintage + junit-vintage-engine + test + + + org.hamcrest + hamcrest-core + + + + + org.apache.commons + commons-lang3 + + + org.springframework.boot + spring-boot-starter-test + test + + + \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/src/main/docker/Dockerfile b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/src/main/docker/Dockerfile new file mode 100644 index 000000000..fbb79d6da --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/src/main/docker/Dockerfile @@ -0,0 +1,16 @@ +FROM openjdk:11-jre-slim as builder +COPY maven/${project.build.finalName}.jar ./ +RUN java -Djarmode=layertools -jar ${project.build.finalName}.jar extract + +FROM openjdk:11-jre-slim +RUN mkdir /workspace +WORKDIR /workspace +COPY --from=builder dependencies/ ./ +COPY --from=builder snapshot-dependencies/ ./ +RUN true +COPY --from=builder spring-boot-loader/ ./ +COPY --from=builder application/ ./ +EXPOSE 8080 +ENV SPRING_PROFILES_ACTIVE=logEvents,inMemoryStorage +ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/.urandom", "org.springframework.boot.loader.JarLauncher"] + diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/src/main/resources/.empty b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/src/main/resources/.empty new file mode 100644 index 000000000..35761803e --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/src/main/resources/.empty @@ -0,0 +1 @@ +Just an empty file so that the project is build \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/Readme.md b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/Readme.md new file mode 100644 index 000000000..077740894 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/Readme.md @@ -0,0 +1,9 @@ +# Submodel Registry Service Release Log MongoDb + +This project creates a docker image based on the specific spring-boot jar file. + +To reduce dependencies and jar file size, we want to produce a docker file for each combination of registry-event-sink and storage combination. + +We log registry events and use MongoDb storage in the docker image created here. + +To test it on your local PC, invoke the build-image script and try the [docker compose file in the sibling project](../docker-compose/docker-compose.yml). \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/pom.xml new file mode 100644 index 000000000..cce0d47f2 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry + ${revision} + .. + + + basyx.submodelregistry-service-release-log-mongodb + + + 2020.0.4 + org.eclipse.digitaltwin.basyx.submodelregistry.service.OpenApiGeneratorApplication + submodel-registry-log-mongodb + + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-mongodb-storage + + + org.junit.vintage + junit-vintage-engine + test + + + org.hamcrest + hamcrest-core + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.apache.commons + commons-lang3 + + + + \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/src/main/docker/Dockerfile b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/src/main/docker/Dockerfile new file mode 100644 index 000000000..90095c44c --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/src/main/docker/Dockerfile @@ -0,0 +1,16 @@ +FROM openjdk:11-jre-slim as builder +COPY maven/${project.build.finalName}.jar ./ +RUN java -Djarmode=layertools -jar ${project.build.finalName}.jar extract + +FROM openjdk:11-jre-slim +RUN mkdir /workspace +WORKDIR /workspace +COPY --from=builder dependencies/ ./ +COPY --from=builder snapshot-dependencies/ ./ +RUN true +COPY --from=builder spring-boot-loader/ ./ +COPY --from=builder application/ ./ +EXPOSE 8080 +ENV SPRING_PROFILES_ACTIVE=logEvents,mongoDbStorage +ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/.urandom", "org.springframework.boot.loader.JarLauncher"] + diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/src/main/resources/.empty b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/src/main/resources/.empty new file mode 100644 index 000000000..35761803e --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/src/main/resources/.empty @@ -0,0 +1 @@ +Just an empty file so that the project is build \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/README.md b/basyx.submodelregistry/basyx.submodelregistry-service/README.md new file mode 100644 index 000000000..1c5a9d1eb --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/README.md @@ -0,0 +1,7 @@ +# Basyx Submodel Registry Service + +The base server implementation was generated by the [openAPI generator](https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator-maven-plugin) project. + +It is based on [Spring Boot](https://spring.io/projects/spring-boot) and logs shell or submodel registration-updates. The storage implementation is missing. You can either use the inMemory implementation or mongoDb implementation provided by sibling projects or implement your storage. + +Use the basyx.submodelregistry-basemodel project if you want to use just POJOs generated out of the OpenAPI description. You can also generate or implement other model classes with specific annotations as we do it for the mongodb-storage implementation. diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/open-api/.gitkeep b/basyx.submodelregistry/basyx.submodelregistry-service/open-api/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-service/pom.xml new file mode 100644 index 000000000..459c90c32 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/pom.xml @@ -0,0 +1,211 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry + ${revision} + .. + + + basyx.submodelregistry-service + + jar + + + 2020.0.4 + ${project.basedir}/${openapi.folder.name} + ${openapi.folder}/${openapi.name} + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + generate-sources + + add-source + + + + ${generated.folder}/java + + + + + + + maven-clean-plugin + + + + ${project.basedir}/${generated.folder} + + **/.gitkeep + + false + + + ${openapi.folder} + + .gitkeep + + false + + + + + + de.dfki.cos.basys.common + jsonpatch-maven-plugin + + + generate-sources + + jsonpatch-maven-plugin + + + ${project.basedir}/../${openapi.folder.name}/${openapi.base.name} + ${project.basedir}/../${openapi.folder.name}/${patch.base-extensions.name} + ${openapi.result.file} + + + + + + org.openapitools + openapi-generator-maven-plugin + + + + generate + + + false + true + + ${openapi.result.file} + spring + spring-boot + ${project.basedir}/${generated.folder} + + ${project.basedir}/templates + + + true + java8 + java + true + true + true + org.eclipse.digitaltwin.basyx.submodelregistry.service.configuration + org.eclipse.digitaltwin.basyx.submodelregistry.service.api + org.eclipse.digitaltwin.basyx.submodelregistry.service + org.eclipse.digitaltwin.basyx.submodelregistry.model + true + springdoc + + + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + org.projectlombok + lombok + true + + + org.springdoc + springdoc-openapi-webmvc-core + 1.7.0 + + + org.springdoc + springdoc-openapi-ui + + + javax.validation + validation-api + + + org.springframework.boot + spring-boot-starter-validation + + + javax.xml.bind + jaxb-api + + + org.springframework.boot + spring-boot-starter-actuator + + + com.google.guava + guava + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-basemodel + provided + + + org.openapitools + jackson-databind-nullable + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + org.hamcrest + hamcrest-core + + + + + org.apache.commons + commons-lang3 + test + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/api/BasyxDescriptionApiDelegate.java b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/api/BasyxDescriptionApiDelegate.java new file mode 100644 index 000000000..d5fabdb98 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/api/BasyxDescriptionApiDelegate.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.api; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.digitaltwin.basyx.submodelregistry.model.ServiceDescription; +import org.eclipse.digitaltwin.basyx.submodelregistry.model.ServiceDescription.ProfilesEnum; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +@Component +public class BasyxDescriptionApiDelegate implements DescriptionApiDelegate { + + private ServiceDescription description; + + @Autowired + public void setValues(@Value("${description.profiles}") String[] profiles) { + description = new ServiceDescription(); + List profilesList = new ArrayList<>(); + for (String eachProfile : profiles) { + ProfilesEnum value = getProfile(eachProfile); + profilesList.add(value); + } + description.setProfiles(profilesList); + } + + private ProfilesEnum getProfile(String eachProfile) { + for (ProfilesEnum b : ProfilesEnum.values()) { + if (b.getValue().equals(eachProfile)) { + return b; + } + } + throw new ProfileNotFoundException(eachProfile); + } + + @Override + public ResponseEntity getDescription() { + return new ResponseEntity<>(description, HttpStatus.OK); + } + + public static class ProfileNotFoundException extends IllegalArgumentException { + + private static final long serialVersionUID = 1L; + + public ProfileNotFoundException(String profile) { + super("No profile found with name: " + profile); + } + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/api/BasyxSubmodelRegistryApiDelegate.java b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/api/BasyxSubmodelRegistryApiDelegate.java new file mode 100644 index 000000000..f9bc4be34 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/api/BasyxSubmodelRegistryApiDelegate.java @@ -0,0 +1,110 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.api; + +import org.eclipse.digitaltwin.basyx.submodelregistry.model.GetSubmodelDescriptorsResult; +import org.eclipse.digitaltwin.basyx.submodelregistry.model.PagedResultPagingMetadata; +import org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.events.RegistryEventSink; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.CursorResult; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.PaginationInfo; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.RegistrationEventSendingSubmodelRegistryStorage; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.SubmodelRegistryStorage; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +@Component +public class BasyxSubmodelRegistryApiDelegate implements SubmodelDescriptorsApiDelegate { + + private final SubmodelRegistryStorage storage; + + public BasyxSubmodelRegistryApiDelegate(SubmodelRegistryStorage storage, RegistryEventSink eventSink) { + this.storage = new RegistrationEventSendingSubmodelRegistryStorage(storage, eventSink); + } + + @Override + public ResponseEntity deleteAllSubmodelDescriptors() { + storage.clear(); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @Override + public ResponseEntity getSubmodelDescriptorById(String submodelIdentifier) { + SubmodelDescriptor submodelDescriptor = storage.getSubmodelDescriptor(submodelIdentifier); + return new ResponseEntity<>(submodelDescriptor, HttpStatus.OK); + } + + @Override + public ResponseEntity deleteSubmodelDescriptorById(String submodelIdentifier) { + storage.removeSubmodelDescriptor(submodelIdentifier); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @Override + public ResponseEntity postSubmodelDescriptor(SubmodelDescriptor submodelDescriptor) { + storage.insertSubmodelDescriptor(submodelDescriptor); + return new ResponseEntity<>(submodelDescriptor, HttpStatus.CREATED); + } + + @Override + public ResponseEntity putSubmodelDescriptorById(String submodelIdentifier, SubmodelDescriptor submodelDescriptor) { + storage.replaceSubmodelDescriptor(submodelIdentifier, submodelDescriptor); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @Override + public ResponseEntity getAllSubmodelDescriptors(Integer limit, String cursor) { + PaginationInfo pInfo = new PaginationInfo(limit, cursor); + CursorResult cResult = storage.getAllSubmodelDescriptors(pInfo); + GetSubmodelDescriptorsResult gsdResult = new GetSubmodelDescriptorsResult(); + gsdResult.setPagingMetadata(new PagedResultPagingMetadata().cursor(cResult.getCursor())); + gsdResult.setResult(cResult.getResult()); + return new ResponseEntity<>(gsdResult, HttpStatus.OK); + } + +// private PagedResultPagingMetadata resolvePagingMeta(CursorResult result) { +// PagedResultPagingMetadata meta = new PagedResultPagingMetadata(); +// String encodedCursor = encodeCursor(result.getCursor()); +// meta.setCursor(encodedCursor); +// return meta; +// } + // + // // we encode and decode the cursor as it is passed as url param and could + // hava invalid chars + // private String encodeCursor(String cursor) { + // if (cursor == null) { + // return null; + // } + // return URLEncoder.encode(cursor, StandardCharsets.UTF_8); + // } + // + // private String decodeCursor(String cursor) { + // if (cursor == null) { + // return null; + // } + // return URLDecoder.decode(cursor, StandardCharsets.UTF_8); + // } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/configuration/RestConfiguration.java b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/configuration/RestConfiguration.java new file mode 100644 index 000000000..569087802 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/configuration/RestConfiguration.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.SerializationFeature; + +@Configuration +public class RestConfiguration { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + @Bean + public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() { + Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder().serializationInclusion(JsonInclude.Include.NON_NULL); + builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + return new MappingJackson2HttpMessageConverter(builder.build()); + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/errors/BasyxControllerAdvice.java b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/errors/BasyxControllerAdvice.java new file mode 100644 index 000000000..bc284d235 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/errors/BasyxControllerAdvice.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.errors; + +import java.time.OffsetDateTime; + +import org.eclipse.digitaltwin.basyx.submodelregistry.model.Message; +import org.eclipse.digitaltwin.basyx.submodelregistry.model.Message.MessageTypeEnum; +import org.eclipse.digitaltwin.basyx.submodelregistry.model.Result; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.server.ResponseStatusException; + +@ControllerAdvice +public class BasyxControllerAdvice { + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleValidationException(MethodArgumentNotValidException ex) { + Result result = new Result(); + OffsetDateTime timestamp = OffsetDateTime.now(); + String reason = HttpStatus.BAD_REQUEST.getReasonPhrase(); + for (ObjectError error : ex.getAllErrors()) { + result.addMessagesItem(new Message().code(reason).messageType(MessageTypeEnum.EXCEPTION).text(error.toString()).timestamp(timestamp)); + } + return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(ResponseStatusException.class) + public ResponseEntity handleExceptions(ResponseStatusException ex) { + return newResultEntity(ex, ex.getStatus()); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleExceptions(Exception ex) { + return newResultEntity(ex, HttpStatus.INTERNAL_SERVER_ERROR); + } + + private ResponseEntity newResultEntity(Exception ex, HttpStatus status) { + Result result = new Result(); + Message message = newExceptionMessage(ex.getMessage(), status); + result.addMessagesItem(message); + return new ResponseEntity<>(result, status); + } + + private Message newExceptionMessage(String msg, HttpStatus status) { + Message message = new Message(); + message.setCode("" + status.value()); + message.setMessageType(MessageTypeEnum.EXCEPTION); + message.setTimestamp(OffsetDateTime.now()); + message.setText(msg); + return message; + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/errors/DescriptorAlreadyExistsException.java b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/errors/DescriptorAlreadyExistsException.java new file mode 100644 index 000000000..23f54fda3 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/errors/DescriptorAlreadyExistsException.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.errors; + +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +public abstract class DescriptorAlreadyExistsException extends ResponseStatusException { + + private static final long serialVersionUID = 1L; + + protected DescriptorAlreadyExistsException(String reason) { + super(HttpStatus.CONFLICT, reason); + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/errors/DescriptorNotFoundException.java b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/errors/DescriptorNotFoundException.java new file mode 100644 index 000000000..d1a9a2b05 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/errors/DescriptorNotFoundException.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.errors; + +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +public abstract class DescriptorNotFoundException extends ResponseStatusException { + + private static final long serialVersionUID = 1L; + + protected DescriptorNotFoundException(String reason) { + super(HttpStatus.NOT_FOUND, reason); + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/errors/SubmodelAlreadyExistsException.java b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/errors/SubmodelAlreadyExistsException.java new file mode 100644 index 000000000..6a0adfda0 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/errors/SubmodelAlreadyExistsException.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.errors; + +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +public class SubmodelAlreadyExistsException extends ResponseStatusException { + + private static final long serialVersionUID = 1L; + + public SubmodelAlreadyExistsException(String submodelId) { + super(HttpStatus.CONFLICT, "The submodel element '" + submodelId + "' already exists."); + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/errors/SubmodelNotFoundException.java b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/errors/SubmodelNotFoundException.java new file mode 100644 index 000000000..c53d3737e --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/errors/SubmodelNotFoundException.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.errors; + +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +public class SubmodelNotFoundException extends ResponseStatusException { + + private static final long serialVersionUID = 1L; + + public SubmodelNotFoundException(String submodelId) { + super(HttpStatus.NOT_FOUND, "Submodel '" + submodelId + "' not found."); + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/events/RegistryEvent.java b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/events/RegistryEvent.java new file mode 100644 index 000000000..491485444 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/events/RegistryEvent.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.events; + +import javax.annotation.Nullable; + +import org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RegistryEvent { + + private String id; + private EventType type; + private @Nullable SubmodelDescriptor submodelDescriptor; + + public enum EventType { + SUBMODEL_REGISTERED, SUBMODEL_UNREGISTERED + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/events/RegistryEventLogSink.java b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/events/RegistryEventLogSink.java new file mode 100644 index 000000000..22537f4be --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/events/RegistryEventLogSink.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.events; + +import org.slf4j.Marker; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@Component +@ConditionalOnProperty(prefix = "events", name = "sink", havingValue = "log") +public class RegistryEventLogSink implements RegistryEventSink { + + @Autowired + private MappingJackson2HttpMessageConverter converter; + + @Override + public void consumeEvent(RegistryEvent evt) { + try { + ObjectMapper objectMapper = converter.getObjectMapper(); + String msg = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(evt); + log.debug("Event sent -> " + msg); + } catch (JsonProcessingException e) { + log.error(Marker.ANY_MARKER, "Failed to process json ", e); + } + } + +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/events/RegistryEventSink.java b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/events/RegistryEventSink.java new file mode 100644 index 000000000..30ab85729 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/events/RegistryEventSink.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.events; + +public interface RegistryEventSink { + + void consumeEvent(RegistryEvent evt); + +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/CursorEncodingRegistryStorage.java b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/CursorEncodingRegistryStorage.java new file mode 100644 index 000000000..8132ca4d0 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/CursorEncodingRegistryStorage.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.storage; + +import java.nio.charset.StandardCharsets; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Delegate; + +@RequiredArgsConstructor +public class CursorEncodingRegistryStorage implements SubmodelRegistryStorage { + + @Delegate + private final SubmodelRegistryStorage storage; + + @Override + public CursorResult getAllSubmodelDescriptors(@NonNull PaginationInfo pRequest) { + PaginationInfo decoded = decodeCursor(pRequest); + CursorResult result = storage.getAllSubmodelDescriptors(decoded); + return encodeCursor(result); + } + + + private CursorResult encodeCursor(CursorResult result) { + String encodedCursor = encodeCursor(result.getCursor()); + return new CursorResult(encodedCursor, result.getResult()); + } + + private PaginationInfo decodeCursor(PaginationInfo info) { + String cursor = decodeCursor(info.getCursor()); + return new PaginationInfo(info.getLimit(), cursor); + } + + private String decodeCursor(String cursor) { + if (cursor == null) { + return null; + } + return new String(java.util.Base64.getUrlDecoder().decode(cursor), StandardCharsets.UTF_8); + } + + private String encodeCursor(String cursor) { + if (cursor == null) { + return null; + } + return new String(java.util.Base64.getUrlEncoder().encode(cursor.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8); + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/CursorResult.java b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/CursorResult.java new file mode 100644 index 000000000..eab23b233 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/CursorResult.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.storage; + +import java.util.List; + +import org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor; + +import lombok.Data; +import lombok.RequiredArgsConstructor; + +@Data +@RequiredArgsConstructor +public class CursorResult { + + final String cursor; + + final List result; + +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/PaginationInfo.java b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/PaginationInfo.java new file mode 100644 index 000000000..fa256f1d2 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/PaginationInfo.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.storage; + +import lombok.Data; +import lombok.RequiredArgsConstructor; + +@Data +@RequiredArgsConstructor +public class PaginationInfo { + + final Integer limit; + final String cursor; + + public boolean hasLimit() { + return limit != null && limit != -1; + } + + public boolean hasCursor() { + return cursor != null; + } + + public boolean isPaged() { + return hasLimit() || hasCursor(); + } + +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/RegistrationEventSendingSubmodelRegistryStorage.java b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/RegistrationEventSendingSubmodelRegistryStorage.java new file mode 100644 index 000000000..1770b5b16 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/RegistrationEventSendingSubmodelRegistryStorage.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.storage; + +import java.util.Set; + +import org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.errors.SubmodelAlreadyExistsException; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.errors.SubmodelNotFoundException; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.events.RegistryEvent; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.events.RegistryEventSink; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class RegistrationEventSendingSubmodelRegistryStorage implements SubmodelRegistryStorage { + + @lombok.experimental.Delegate + private final SubmodelRegistryStorage storage; + + @NonNull + private final RegistryEventSink eventSink; + + @Override + public void insertSubmodelDescriptor(SubmodelDescriptor descr) throws SubmodelAlreadyExistsException { + storage.insertSubmodelDescriptor(descr); + submodelRegistered(descr); + } + + @Override + public void removeSubmodelDescriptor(String submodelId) throws SubmodelNotFoundException { + storage.removeSubmodelDescriptor(submodelId); + submodelUnregistered(submodelId); + } + + @Override + public void replaceSubmodelDescriptor(String submodelId, SubmodelDescriptor descr) throws SubmodelNotFoundException { + storage.replaceSubmodelDescriptor(submodelId, descr); + if (!submodelId.equals(descr.getId())) { + submodelUnregistered(submodelId); + } + submodelRegistered(descr); + } + + + @Override + public Set clear() { + Set removed = storage.clear(); + removed.forEach(this::submodelUnregistered); + return removed; + } + + private void submodelRegistered(SubmodelDescriptor submodel) { + RegistryEvent evt = RegistryEvent.builder().id(submodel.getId()).type(RegistryEvent.EventType.SUBMODEL_REGISTERED).submodelDescriptor(submodel).build(); + eventSink.consumeEvent(evt); + } + + private void submodelUnregistered(@NonNull String submodelId) { + RegistryEvent evt = RegistryEvent.builder().id(submodelId).type(RegistryEvent.EventType.SUBMODEL_UNREGISTERED).build(); + eventSink.consumeEvent(evt); + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/SubmodelRegistryStorage.java b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/SubmodelRegistryStorage.java new file mode 100644 index 000000000..fefc80c51 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/SubmodelRegistryStorage.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.storage; + +import java.util.Set; + +import org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.errors.SubmodelAlreadyExistsException; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.errors.SubmodelNotFoundException; + +public interface SubmodelRegistryStorage { + + CursorResult getAllSubmodelDescriptors(PaginationInfo pRequest); + + SubmodelDescriptor getSubmodelDescriptor(String submodelId) throws SubmodelNotFoundException; + + void insertSubmodelDescriptor(SubmodelDescriptor descr) throws SubmodelAlreadyExistsException; + + void replaceSubmodelDescriptor(String submodelId, SubmodelDescriptor descr) throws SubmodelNotFoundException; + + void removeSubmodelDescriptor(String submodelId) throws SubmodelNotFoundException; + + Set clear(); + +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/src/main/resources/application-logEvents.yml b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/resources/application-logEvents.yml new file mode 100644 index 000000000..ce1d7f465 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/resources/application-logEvents.yml @@ -0,0 +1,3 @@ +--- +events: + sink: log \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/src/main/resources/application.yml b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/resources/application.yml new file mode 100644 index 000000000..aa0ed074b --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/src/main/resources/application.yml @@ -0,0 +1,48 @@ +--- +events: + sink: +description: + profiles: https://admin-shell.io/aas/API/3/0/SubmodelRegistryServiceSpecification/SSP-001 +springdoc: + api-docs: + path: /api-docs +springfox: + documentation: + enabled: true + # open-api.v3.path: /api-docs +management: + endpoints: + web: + exposure: + include: "health,metrics" +logging: + level: + root: INFO +server: + port: 8080 + servlet: + contextPath: /api/v3.0 + error: + whitelabel: + enabled: false +servlet: + headers: [] +spring: + application: + name: Basyx Submodel Registry + jackson: + date-format: org.eclipse.digitaltwin.basyx.submodelregistry.service.RFC3339DateFormat + serialization: + WRITE_DATES_AS_TIMESTAMPS: false + cloud: + stream: + bindings: + default: + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + value-deserializer: org.apache.kafka.common.serialization.StringDeserializer + registryBinding: + destination: submodel-registry + content-type: application/json + producer: + partitionKeyExpression: payload.id + partitionCount: 1 \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/DescriptionProfilesTest.java b/basyx.submodelregistry/basyx.submodelregistry-service/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/DescriptionProfilesTest.java new file mode 100644 index 000000000..f537605b6 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/tests/DescriptionProfilesTest.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelregistry.service.tests; + +import static org.junit.Assert.assertThrows; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.digitaltwin.basyx.submodelregistry.service.api.BasyxDescriptionApiDelegate; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.api.BasyxDescriptionApiDelegate.ProfileNotFoundException; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.api.DescriptionApiController; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.api.DescriptionApiDelegate; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.events.RegistryEventSink; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.SubmodelRegistryStorage; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +@RunWith(SpringRunner.class) +@WebMvcTest(DescriptionApiController.class) +@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) +@TestPropertySource(properties = { "description.profiles=https://admin-shell.io/aas/API/3/0/SubmodelRegistryServiceSpecification/SSP-001,https://admin-shell.io/aas/API/3/0/DiscoveryServiceSpecification/SSP-001" }) +public class DescriptionProfilesTest { + + @MockBean + public SubmodelRegistryStorage storage; + + @MockBean + public RegistryEventSink eventSink; + + @Autowired + public DescriptionApiDelegate delegate; + + @Autowired + private MockMvc mvc; + + @Test + public void whenGetDescription_ThenSuccess() throws Exception { + this.mvc.perform(get("/description").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.profiles").value(getDefinedProfiles())); + } + + @Test + public void whenWrongConfiguration_FailedToMapToEnum() { + assertThrows(ProfileNotFoundException.class, + () -> ((BasyxDescriptionApiDelegate) delegate).setValues(new String[] { "Unknown-Value" })); + } + + private List getDefinedProfiles() { + TestPropertySource src = DescriptionProfilesTest.class.getAnnotation(TestPropertySource.class); + String profilesDef = src.properties()[0]; + String[] definedProfiles = profilesDef.split("=")[1].split(","); + return Arrays.stream(definedProfiles).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/templates/api.mustache b/basyx.submodelregistry/basyx.submodelregistry-service/templates/api.mustache new file mode 100644 index 000000000..24e5bf310 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/templates/api.mustache @@ -0,0 +1,270 @@ +/** + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech) ({{{generatorVersion}}}). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +package {{package}}; + +{{#imports}}import {{import}}; +{{/imports}} +{{#swagger2AnnotationLibrary}} +import io.swagger.v3.oas.annotations.ExternalDocumentation; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +{{/swagger2AnnotationLibrary}} +{{#swagger1AnnotationLibrary}} +import io.swagger.annotations.*; +{{/swagger1AnnotationLibrary}} +{{#jdk8-no-delegate}} +{{#virtualService}} +import io.virtualan.annotation.ApiVirtual; +import io.virtualan.annotation.VirtualService; +{{/virtualService}} +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +{{/jdk8-no-delegate}} +{{^useResponseEntity}} +import org.springframework.http.HttpStatus; +{{/useResponseEntity}} +{{#useResponseEntity}} +import org.springframework.http.ResponseEntity; +{{/useResponseEntity}} +{{#useBeanValidation}} +import org.springframework.validation.annotation.Validated; +{{/useBeanValidation}} +{{#useSpringController}} +{{#useResponseEntity}} +import org.springframework.stereotype.Controller; +{{/useResponseEntity}} +{{^useResponseEntity}} +import org.springframework.web.bind.annotation.RestController; +{{/useResponseEntity}} +{{/useSpringController}} +import org.springframework.web.bind.annotation.*; +{{#jdk8-no-delegate}} +{{^reactive}} +import org.springframework.web.context.request.NativeWebRequest; +{{/reactive}} +{{/jdk8-no-delegate}} +import org.springframework.web.multipart.MultipartFile; +{{#reactive}} +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import org.springframework.http.codec.multipart.Part; +{{/reactive}} + +{{#useBeanValidation}} +import {{javaxPackage}}.validation.Valid; +import {{javaxPackage}}.validation.constraints.*; +{{/useBeanValidation}} +import java.util.List; +import java.util.Map; +{{#jdk8-no-delegate}} +import java.util.Optional; +{{/jdk8-no-delegate}} +{{^jdk8-no-delegate}} +{{#useOptional}} +import java.util.Optional; +{{/useOptional}} +{{/jdk8-no-delegate}} +{{#async}} +import java.util.concurrent.CompletableFuture; +{{/async}} +import {{javaxPackage}}.annotation.Generated; + +{{>generatedAnnotation}} +{{#useBeanValidation}} +@Validated +{{/useBeanValidation}} +{{#useSpringController}} +{{#useResponseEntity}} +@Controller +{{/useResponseEntity}} +{{^useResponseEntity}} +@RestController +{{/useResponseEntity}} +{{/useSpringController}} +{{#swagger2AnnotationLibrary}} +@Tag(name = "{{{tagName}}}", description = {{#tagDescription}}"{{{.}}}"{{/tagDescription}}{{^tagDescription}}"the {{{tagName}}} API"{{/tagDescription}}) +{{/swagger2AnnotationLibrary}} +{{#swagger1AnnotationLibrary}} +@Api(value = "{{{tagName}}}", description = {{#tagDescription}}"{{{.}}}"{{/tagDescription}}{{^tagDescription}}"the {{{tagName}}} API"{{/tagDescription}}) +{{/swagger1AnnotationLibrary}} +{{#operations}} +{{#virtualService}} +@VirtualService +{{/virtualService}} +{{#useRequestMappingOnInterface}} +{{=<% %>=}} +@RequestMapping("${openapi.<%title%>.base-path:<%>defaultBasePath%>}") +<%={{ }}=%> +{{/useRequestMappingOnInterface}} +public interface {{classname}} { +{{#jdk8-default-interface}} + {{^isDelegate}} + {{^reactive}} + + default Optional getRequest() { + return Optional.empty(); + } + {{/reactive}} + {{/isDelegate}} + {{#isDelegate}} + + default {{classname}}Delegate getDelegate() { + return new {{classname}}Delegate() {}; + } + {{/isDelegate}} +{{/jdk8-default-interface}} +{{#operation}} + + /** + * {{httpMethod}} {{{path}}}{{#summary}} : {{.}}{{/summary}} + {{#notes}} + * {{.}} + {{/notes}} + * + {{#allParams}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}} + {{/allParams}} + * @return {{#responses}}{{message}} (status code {{code}}){{^-last}} + * or {{/-last}}{{/responses}} + {{#isDeprecated}} + * @deprecated + {{/isDeprecated}} + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + {{#isDeprecated}} + @Deprecated + {{/isDeprecated}} + {{#virtualService}} + @ApiVirtual + {{/virtualService}} + {{#swagger2AnnotationLibrary}} + @Operation( + operationId = "{{{operationId}}}", + {{#summary}} + summary = "{{{.}}}", + {{/summary}} + {{#notes}} + description = "{{{.}}}", + {{/notes}} + {{#vendorExtensions.x-tags.size}} + tags = { {{#vendorExtensions.x-tags}}"{{tag}}"{{^-last}}, {{/-last}}{{/vendorExtensions.x-tags}} }, + {{/vendorExtensions.x-tags.size}} + responses = { + {{#responses}} + @ApiResponse(responseCode = {{#isDefault}}"default"{{/isDefault}}{{^isDefault}}"{{{code}}}"{{/isDefault}}, description = "{{{message}}}"{{#baseType}}, content = { + {{#produces}} + @Content(mediaType = "{{{mediaType}}}", {{#isArray}}array = @ArraySchema({{/isArray}}schema = @Schema(implementation = {{{baseType}}}.class){{#isArray}}){{/isArray}}){{^-last}},{{/-last}} + {{/produces}} + }{{/baseType}}){{^-last}},{{/-last}} + {{/responses}} + }{{#hasAuthMethods}}, + security = { + {{#authMethods}} + @SecurityRequirement(name = "{{name}}"{{#isOAuth}}, scopes={ {{#scopes}}"{{scope}}"{{^-last}}, {{/-last}}{{/scopes}} }{{/isOAuth}}){{^-last}},{{/-last}} + {{/authMethods}} + }{{/hasAuthMethods}}{{#externalDocs}}, + externalDocs = @ExternalDocumentation(description = "{{externalDocs.description}}", url = "{{externalDocs.url}}"){{/externalDocs}} + ) + {{/swagger2AnnotationLibrary}} + {{#swagger1AnnotationLibrary}} + @ApiOperation( + {{#vendorExtensions.x-tags.size}} + tags = { {{#vendorExtensions.x-tags}}"{{tag}}"{{^-last}}, {{/-last}}{{/vendorExtensions.x-tags}} }, + {{/vendorExtensions.x-tags.size}} + value = "{{{summary}}}", + nickname = "{{{operationId}}}", + notes = "{{{notes}}}"{{#returnBaseType}}, + response = {{{.}}}.class{{/returnBaseType}}{{#returnContainer}}, + responseContainer = "{{{.}}}"{{/returnContainer}}{{#hasAuthMethods}}, + authorizations = { + {{#authMethods}} + {{#isOAuth}} + @Authorization(value = "{{name}}", scopes = { + {{#scopes}} + @AuthorizationScope(scope = "{{scope}}", description = "{{description}}"){{^-last}},{{/-last}} + {{/scopes}} + }){{^-last}},{{/-last}} + {{/isOAuth}} + {{^isOAuth}} + @Authorization(value = "{{name}}"){{^-last}},{{/-last}} + {{/isOAuth}} + {{/authMethods}} }{{/hasAuthMethods}} + ) + @ApiResponses({ + {{#responses}} + @ApiResponse(code = {{{code}}}, message = "{{{message}}}"{{#baseType}}, response = {{{.}}}.class{{/baseType}}{{#containerType}}, responseContainer = "{{{.}}}"{{/containerType}}){{^-last}},{{/-last}} + {{/responses}} + }) + {{/swagger1AnnotationLibrary}} + {{#implicitHeadersParams.0}} + {{#swagger2AnnotationLibrary}} + @Parameters({ + {{#implicitHeadersParams}} + {{>paramDoc}}{{^-last}},{{/-last}} + {{/implicitHeadersParams}} + }) + {{/swagger2AnnotationLibrary}} + {{#swagger1AnnotationLibrary}} + @ApiImplicitParams({ + {{#implicitHeadersParams}} + {{>implicitHeader}}{{^-last}},{{/-last}} + {{/implicitHeadersParams}} + }) + {{/swagger1AnnotationLibrary}} + {{/implicitHeadersParams.0}} + @RequestMapping( + method = RequestMethod.{{httpMethod}}, + value = "{{{path}}}"{{#singleContentTypes}}{{#hasProduces}}, + produces = "{{{vendorExtensions.x-accepts}}}"{{/hasProduces}}{{#hasConsumes}}, + consumes = "{{{vendorExtensions.x-content-type}}}"{{/hasConsumes}}{{/singleContentTypes}}{{^singleContentTypes}}{{#hasProduces}}, + produces = { {{#produces}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/produces}} }{{/hasProduces}}{{#hasConsumes}}, + consumes = { {{#consumes}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/consumes}} }{{/hasConsumes}}{{/singleContentTypes}}{{#hasVersionHeaders}}, + headers = { {{#vendorExtensions.versionHeaderParamsList}}"{{baseName}}{{#defaultValue}}={{{.}}}{{/defaultValue}}"{{^-last}}, {{/-last}}{{/vendorExtensions.versionHeaderParamsList}} } {{/hasVersionHeaders}}{{#hasVersionQueryParams}}, + params = { {{#vendorExtensions.versionQueryParamsList}}"{{baseName}}{{#defaultValue}}={{{.}}}{{/defaultValue}}"{{^-last}}, {{/-last}}{{/vendorExtensions.versionQueryParamsList}} } {{/hasVersionQueryParams}} + ) + {{^useResponseEntity}} + @ResponseStatus({{#springHttpStatus}}{{#responses.0}}{{{code}}}{{/responses.0}}{{/springHttpStatus}}) + {{/useResponseEntity}} + {{#jdk8-default-interface}}default {{/jdk8-default-interface}}{{#responseWrapper}}{{.}}<{{/responseWrapper}}{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}}{{#responseWrapper}}>{{/responseWrapper}} {{#delegate-method}}_{{/delegate-method}}{{operationId}}( + {{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{>cookieParams}}{{^-last}}, + {{/-last}}{{/allParams}}{{#reactive}}{{#hasParams}}, + {{/hasParams}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true){{/swagger2AnnotationLibrary}}{{#springFoxDocumentationProvider}}@ApiIgnore{{/springFoxDocumentationProvider}} final ServerWebExchange exchange{{/reactive}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, + {{/hasParams}}{{^hasParams}}{{#reactive}},{{/reactive}}{{/hasParams}}{{#springFoxDocumentationProvider}}@ApiIgnore {{/springFoxDocumentationProvider}}{{#springDocDocumentationProvider}}@ParameterObject {{/springDocDocumentationProvider}}final Pageable pageable{{/vendorExtensions.x-spring-paginated}} + ){{#unhandledException}} throws Exception{{/unhandledException}}{{^jdk8-default-interface}};{{/jdk8-default-interface}}{{#jdk8-default-interface}} { + {{#delegate-method}} + {{^isVoid}}return {{/isVoid}}{{#isVoid}}{{#useResponseEntity}}return {{/useResponseEntity}}{{^useResponseEntity}}{{#reactive}}return {{/reactive}}{{/useResponseEntity}}{{/isVoid}}{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#reactive}}{{#hasParams}}, {{/hasParams}}exchange{{/reactive}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}}, {{/reactive}}{{/hasParams}}pageable{{/vendorExtensions.x-spring-paginated}}); + } + + // Override this method + {{#jdk8-default-interface}}default {{/jdk8-default-interface}} {{#responseWrapper}}{{.}}<{{/responseWrapper}}{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}}{{#responseWrapper}}>{{/responseWrapper}} {{operationId}}({{#allParams}}{{^isFile}}{{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{{dataType}}}{{/reactive}}{{#reactive}}{{^isArray}}Mono<{{{dataType}}}>{{/isArray}}{{#isArray}}Flux<{{{baseType}}}>{{/isArray}}{{/reactive}}{{/isBodyParam}}{{/isFile}}{{#isFile}}{{#reactive}}Flux{{/reactive}}{{^reactive}}MultipartFile{{/reactive}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#reactive}}{{#hasParams}}, {{/hasParams}}{{#springFoxDocumentationProvider}}@ApiIgnore{{/springFoxDocumentationProvider}} final ServerWebExchange exchange{{/reactive}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}}, {{/reactive}}{{/hasParams}}{{#springFoxDocumentationProvider}}@ApiIgnore{{/springFoxDocumentationProvider}}final Pageable pageable{{/vendorExtensions.x-spring-paginated}}){{#unhandledException}} throws Exception{{/unhandledException}} { + {{/delegate-method}} + {{^isDelegate}} + {{>methodBody}} + {{/isDelegate}} + {{#isDelegate}} + {{#allParams}} + {{#vendorExtensions.x-utf8-base64-url-encoded-as-string}} + String {{paramName}}FromBase64EncodedParam = {{{paramName}}} == null ? null : new String(java.util.Base64.getUrlDecoder().decode({{paramName}}), java.nio.charset.StandardCharsets.UTF_8); + {{/vendorExtensions.x-utf8-base64-url-encoded-as-string}} + {{/allParams}} + {{^isVoid}}return {{/isVoid}}{{#isVoid}}{{#useResponseEntity}}return {{/useResponseEntity}}{{^useResponseEntity}}{{#reactive}}return {{/reactive}}{{/useResponseEntity}}{{/isVoid}}getDelegate().{{operationId}}({{#allParams}}{{paramName}}{{#vendorExtensions.x-utf8-base64-url-encoded-as-string}}FromBase64EncodedParam{{/vendorExtensions.x-utf8-base64-url-encoded-as-string}}{{^-last}}, {{/-last}}{{/allParams}}{{#reactive}}{{#hasParams}}, {{/hasParams}}exchange{{/reactive}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}}, {{/reactive}}{{/hasParams}}pageable{{/vendorExtensions.x-spring-paginated}});{{/isDelegate}} + }{{/jdk8-default-interface}} + +{{/operation}} +} +{{/operations}} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/templates/apiDelegate.mustache b/basyx.submodelregistry/basyx.submodelregistry-service/templates/apiDelegate.mustache new file mode 100644 index 000000000..d033161ef --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-service/templates/apiDelegate.mustache @@ -0,0 +1,73 @@ +package {{package}}; + +{{#imports}}import {{import}}; +{{/imports}} +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +{{#useResponseEntity}} +import org.springframework.http.ResponseEntity; +{{/useResponseEntity}} +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.multipart.MultipartFile; +{{#reactive}} +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import org.springframework.http.codec.multipart.Part; +{{/reactive}} + +import java.util.List; +import java.util.Map; +import java.util.Optional; +{{#async}} +import java.util.concurrent.CompletableFuture; +{{/async}} +import {{javaxPackage}}.annotation.Generated; + +{{#operations}} +/** + * A delegate to be called by the {@link {{classname}}Controller}}. + * Implement this interface with a {@link org.springframework.stereotype.Service} annotated class. + */ +{{>generatedAnnotation}} +public interface {{classname}}Delegate { +{{#jdk8-default-interface}} + + default Optional getRequest() { + return Optional.empty(); + } +{{/jdk8-default-interface}} + +{{#operation}} + /** + * {{httpMethod}} {{{path}}}{{#summary}} : {{.}}{{/summary}} + {{#notes}} + * {{.}} + {{/notes}} + * + {{#allParams}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}} + {{/allParams}} + * @return {{#responses}}{{message}} (status code {{code}}){{^-last}} + * or {{/-last}}{{/responses}} + {{#isDeprecated}} + * @deprecated + {{/isDeprecated}} + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + * @see {{classname}}#{{operationId}} + */ + {{#isDeprecated}} + @Deprecated + {{/isDeprecated}} + {{#jdk8-default-interface}}default {{/jdk8-default-interface}}{{#responseWrapper}}{{.}}<{{/responseWrapper}}{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}}{{#responseWrapper}}>{{/responseWrapper}} {{operationId}}({{#allParams}}{{^isFile}}{{^isBodyParam}}{{#vendorExtensions.x-utf8-base64-url-encoded-as-string}} String{{/vendorExtensions.x-utf8-base64-url-encoded-as-string}}{{^vendorExtensions.x-utf8-base64-url-encoded-as-string}} {{>optionalDataType}}{{/vendorExtensions.x-utf8-base64-url-encoded-as-string}} {{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{{dataType}}}{{/reactive}}{{#reactive}}{{^isArray}}Mono<{{{dataType}}}>{{/isArray}}{{#isArray}}Flux<{{{baseType}}}>{{/isArray}}{{/reactive}}{{/isBodyParam}}{{/isFile}}{{#isFile}}{{#isArray}}List<{{/isArray}}{{#reactive}}Flux{{/reactive}}{{^reactive}}MultipartFile{{/reactive}}{{#isArray}}>{{/isArray}}{{/isFile}} {{paramName}}{{^-last}}, + {{/-last}}{{/allParams}}{{#reactive}}{{#hasParams}}, + {{/hasParams}}ServerWebExchange exchange{{/reactive}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}}, {{/reactive}}{{/hasParams}}final Pageable pageable{{/vendorExtensions.x-spring-paginated}}){{#unhandledException}} throws Exception{{/unhandledException}}{{^jdk8-default-interface}};{{/jdk8-default-interface}}{{#jdk8-default-interface}} { + {{>methodBody}} + }{{/jdk8-default-interface}} + +{{/operation}} +} +{{/operations}} \ No newline at end of file diff --git a/basyx.submodelregistry/docker-compose/GenerateByteUrlEncodedBase64Id.java b/basyx.submodelregistry/docker-compose/GenerateByteUrlEncodedBase64Id.java new file mode 100644 index 000000000..8c5bdc54c --- /dev/null +++ b/basyx.submodelregistry/docker-compose/GenerateByteUrlEncodedBase64Id.java @@ -0,0 +1,26 @@ +package org.eclipse.digitaltwin.basyx.aasregistry.service; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +public class GenerateByteUrlEncodedBase64Id { + + public static void main(String[] args) throws UnsupportedEncodingException { + if (args.length != 1) { + throw new IllegalArgumentException("One argument expected!"); + } + String id = args[0]; + byte[] values = Base64.getUrlEncoder().encode(id.getBytes(StandardCharsets.UTF_8)); + System.out.print('['); + + for (int i = 0; i < values.length; i++) { + System.out.print(values[i]); + if (i+1 != values.length) { + System.out.print(", "); + } + } + System.out.print(']'); + } + +} diff --git a/basyx.submodelregistry/docker-compose/Readme.md b/basyx.submodelregistry/docker-compose/Readme.md new file mode 100644 index 000000000..653b2373d --- /dev/null +++ b/basyx.submodelregistry/docker-compose/Readme.md @@ -0,0 +1,13 @@ +# Docker Compose + +The folder content is an example on how to include the submodel-registry docker images in a docker compose setup. + +Look at the docker-compose file to see how additional application properties can be applied. + +Start your docker daemon and 'cd' into this folder from your shell. + +Run *build-images.sh* to create the referenced docker images with a specific docker-name-prefix *submodelregistry-test* for testing and call *docker-compose-up.sh* to start the docker stack and *docker-compose-down-sh* to tear it down again. + +After invoking the startup script, follow the links, shown in the console, to the submodel-registry Swagger-UI to test the backend. + +The submodel ids are base64 url encoded and transmitted as bytes. Run 'java GenerateByteUrlEncodedBase64Id.java MyId' (java 11 is required) to create a byte array of your id (here 'MyId') and use the output as path variable at these byte array path positions in Swagger-Docs-UI. \ No newline at end of file diff --git a/basyx.submodelregistry/docker-compose/build-images.sh b/basyx.submodelregistry/docker-compose/build-images.sh new file mode 100644 index 000000000..4d65187a9 --- /dev/null +++ b/basyx.submodelregistry/docker-compose/build-images.sh @@ -0,0 +1,11 @@ +#!/bin/sh +OLD_WORK_DIR=$(pwd) +trap 'cd $OLD_WORK_DIR' EXIT + +cd $(dirname "${BASH_SOURCE[0]}")/.. + +MAVEN_OPS='-Xmx2048 -Xms1024' mvn clean install -DskipTests -Ddocker.username=submodel-registry-test -Ddocker.password="" + +cd $OLD_WORK_DIR + + diff --git a/basyx.submodelregistry/docker-compose/docker-compose-down.sh b/basyx.submodelregistry/docker-compose/docker-compose-down.sh new file mode 100644 index 000000000..49171da79 --- /dev/null +++ b/basyx.submodelregistry/docker-compose/docker-compose-down.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +OLD_WORK_DIR=$(pwd) +trap 'cd $OLD_WORK_DIR' EXIT + +cd $(dirname "${BASH_SOURCE[0]}") + +docker-compose down + +cd $OLD_WORK_DIR \ No newline at end of file diff --git a/basyx.submodelregistry/docker-compose/docker-compose-up.sh b/basyx.submodelregistry/docker-compose/docker-compose-up.sh new file mode 100644 index 000000000..98079f1e8 --- /dev/null +++ b/basyx.submodelregistry/docker-compose/docker-compose-up.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +OLD_WORK_DIR=$(pwd) +trap 'cd $OLD_WORK_DIR' EXIT + +cd $(dirname "${BASH_SOURCE[0]}") + +docker-compose up -d --build --force-recreate + +echo Done! +echo "" +echo Portainer: http://localhost:9091 +echo AKHQ: http://localhost:8087 +echo MongoExpress: http://localhost:8082 +echo Registry - kafka,mongodb: http://localhost:8021/api/v3.0 +echo Registry - kafka,mem: http://localhost:8031/api/v3.0 +echo Registry - log,mongodb: http://localhost:8051/api/v3.0 +echo Registry - log,mem: http://localhost:8041/api/v3.0 +echo "" +read -p "Press any key to continue... " -n1 -s +cd $OLD_WORK_DIR diff --git a/basyx.submodelregistry/docker-compose/docker-compose.yml b/basyx.submodelregistry/docker-compose/docker-compose.yml new file mode 100644 index 000000000..10c8a90e8 --- /dev/null +++ b/basyx.submodelregistry/docker-compose/docker-compose.yml @@ -0,0 +1,163 @@ +version: '3.6' + +networks: + basyx-submodel: + internal: false + mongo-submodel: + internal: false + +volumes: + zookeeper-data: + driver: local + zookeeper-log: + driver: local + kafka-data: + driver: local + mongodb-data-5.0.10: + driver: local + submodel-config: + +services: + portainer: + image: portainer/portainer-ce:2.0.1 + container_name: portainer + restart: always + ports: + - "9091:9000" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + networks: + - basyx-submodel + + zookeeper: + image: zookeeper:3.6 + container_name: zookeeper + restart: always + ports: + - "2182:2181" + volumes: + - zookeeper-data:/var/lib/zookeeper/data + - zookeeper-log:/var/lib/zookeeper/log + networks: + - basyx-submodel + + kafka: + image: confluentinc/cp-kafka:6.2.1 + container_name: kafka + volumes: + - kafka-data:/var/lib/kafka + ports: + - "9093:9092" + environment: + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT + KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9093 + KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'true' + links: + - zookeeper + restart: always + networks: + - basyx-submodel + + akhq: + image: tchiotludo/akhq:0.24.0 + container_name: akhq + environment: + AKHQ_CONFIGURATION: | + akhq: + connections: + docker-kafka-server: + properties: + bootstrap.servers: "kafka:29092" + ports: + - 8087:8080 + restart: always + depends_on: + - kafka + networks: + - basyx-submodel + + mongodb: + image: mongo:5.0.10 + container_name: mongodb + environment: + MONGO_INITDB_ROOT_USERNAME: admin + MONGO_INITDB_ROOT_PASSWORD: admin + ports: + - 27018:27017 + volumes: + - mongodb-data-5.0.10:/data/db + networks: + - basyx-submodel + + mongo-express: + image: mongo-express:1.0.0-alpha.4 + container_name: mongo-express + environment: + ME_CONFIG_MONGODB_SERVER: mongodb + ME_CONFIG_MONGODB_ADMINUSERNAME: admin + ME_CONFIG_MONGODB_ADMINPASSWORD: admin + ME_CONFIG_MONGODB_URL: mongodb://admin:admin@mongodb:27017/ + ports: + - "0.0.0.0:8082:8081" + networks: + - basyx-submodel + restart: always + depends_on: + - mongodb + + submodel-registry-kafka-mongodb: + image: submodel-registry-test/submodel-registry-kafka-mongodb:latest + container_name: submodel-registry-kafka-mongodb + pull_policy: never + ports: + - "8021:8080" + depends_on: + - mongodb + - kafka + restart: always + environment: + KAFKA_BOOTSTRAP_SERVERS: PLAINTEXT://kafka:29092 + SPRING_DATA_MONGODB_URI: mongodb://admin:admin@mongodb:27017 + networks: + - basyx-submodel + + submodel-registry-kafka-mem: + image: submodel-registry-test/submodel-registry-kafka-mem:latest + container_name: submodel-registry-kafka-mem + pull_policy: never + ports: + - "8031:8080" + depends_on: + - kafka + restart: always + environment: + KAFKA_BOOTSTRAP_SERVERS: PLAINTEXT://kafka:29092 + networks: + - basyx-submodel + + submodel-registry-log-mem: + image: submodel-registry-test/submodel-registry-log-mem:latest + container_name: submodel-registry-log-mem + pull_policy: never + ports: + - "8041:8080" + restart: always + networks: + - basyx-submodel + + submodel-registry-log-mongodb: + image: submodel-registry-test/submodel-registry-log-mongodb:latest + container_name: submodel-registry-log-mongodb + pull_policy: never + ports: + - "8051:8080" + depends_on: + - mongodb + restart: always + environment: + SPRING_DATA_MONGODB_URI: mongodb://admin:admin@mongodb:27017 + networks: + - basyx-submodel diff --git a/basyx.submodelregistry/open-api/Plattform_i40-SubmodelRegistryServiceSpecification-V3.0.1_SSP-001-resolved-altered.yaml b/basyx.submodelregistry/open-api/Plattform_i40-SubmodelRegistryServiceSpecification-V3.0.1_SSP-001-resolved-altered.yaml new file mode 100644 index 000000000..64d6fe513 --- /dev/null +++ b/basyx.submodelregistry/open-api/Plattform_i40-SubmodelRegistryServiceSpecification-V3.0.1_SSP-001-resolved-altered.yaml @@ -0,0 +1,952 @@ +--- +openapi: 3.0.3 +info: + title: DotAAS Part 2 | HTTP/REST | Submodel Registry Service Specification + description: "The Full Profile of the Submodel Registry Service Specification as part of the [Specification of the Asset Administration Shell: Part 2](http://industrialdigitaltwin.org/en/content-hub). \nPublisher: Industrial Digital Twin Association (IDTA) April 2023" + contact: + name: Industrial Digital Twin Association (IDTA) + email: info@idtwin.org + license: + name: CC BY 4.0 + url: https://creativecommons.org/licenses/by/4.0/ + version: V3.0.1_SSP-001 +servers: +- url: "{protocol}://{host_name}:{port}/api/{version_prefix}" + variables: + protocol: + description: Allows access through http and https (recommended) + default: https + enum: + - http + - https + host_name: + description: Hostname of server hosting the api + default: admin-shell.io + port: + description: "80 is default for http, 443 for https" + default: "443" + enum: + - "80" + - "443" + version_prefix: + default: v3.0 + enum: + - v3.0 +paths: + /submodel-descriptors: + get: + tags: + - Submodel Registry API + summary: Returns all Submodel Descriptors + operationId: GetAllSubmodelDescriptors + parameters: + - name: limit + in: query + description: The maximum number of elements in the response array + required: false + schema: + minimum: 1 + type: integer + - name: cursor + in: query + description: A server-generated identifier retrieved from pagingMetadata that specifies from which position the result listing should continue + required: false + schema: + type: string + responses: + "200": + description: Requested Submodel Descriptors + content: + application/json: + schema: + $ref: '#/components/schemas/GetSubmodelDescriptorsResult' + "400": + description: "Bad Request, e.g. the request parameters of the format of the request body is wrong." + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "404": + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + default: + description: Default error handling for unmentioned status codes + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + x-semanticIds: + - https://admin-shell.io/aas/API/GetAllSubmodelDescriptors/3/0 + post: + tags: + - Submodel Registry API + summary: "Creates a new Submodel Descriptor, i.e. registers a submodel" + operationId: PostSubmodelDescriptor + requestBody: + description: Submodel Descriptor object + content: + application/json: + schema: + $ref: '#/components/schemas/SubmodelDescriptor' + required: true + responses: + "201": + description: Submodel Descriptor created successfully + headers: + Location: + description: URL of the newly created resource + style: simple + explode: false + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/SubmodelDescriptor' + "400": + description: "Bad Request, e.g. the request parameters of the format of the request body is wrong." + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "409": + description: "Conflict, a resource which shall be created exists already. Might be thrown if a Submodel or SubmodelElement with the same ShortId is contained in a POST request." + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + default: + description: Default error handling for unmentioned status codes + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + x-semanticIds: + - https://admin-shell.io/aas/API/PostSubmodelDescriptor/3/0 + /submodel-descriptors/{submodelIdentifier}: + get: + tags: + - Submodel Registry API + summary: Returns a specific Submodel Descriptor + operationId: GetSubmodelDescriptorById + parameters: + - name: submodelIdentifier + in: path + description: The Submodel’s unique id (UTF8-BASE64-URL-encoded) + required: true + style: simple + explode: false + schema: + type: string + format: byte + responses: + "200": + description: Requested Submodel Descriptor + content: + application/json: + schema: + $ref: '#/components/schemas/SubmodelDescriptor' + "400": + description: "Bad Request, e.g. the request parameters of the format of the request body is wrong." + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "404": + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + default: + description: Default error handling for unmentioned status codes + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + x-semanticIds: + - https://admin-shell.io/aas/API/GetSubmodelDescriptorById/3/0 + put: + tags: + - Submodel Registry API + summary: Updates an existing Submodel Descriptor + operationId: PutSubmodelDescriptorById + parameters: + - name: submodelIdentifier + in: path + description: The Submodel’s unique id (UTF8-BASE64-URL-encoded) + required: true + style: simple + explode: false + schema: + type: string + format: byte + requestBody: + description: Submodel Descriptor object + content: + application/json: + schema: + $ref: '#/components/schemas/SubmodelDescriptor' + required: true + responses: + "204": + description: Submodel Descriptor updated successfully + "400": + description: "Bad Request, e.g. the request parameters of the format of the request body is wrong." + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "404": + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + default: + description: Default error handling for unmentioned status codes + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + x-semanticIds: + - https://admin-shell.io/aas/API/PutSubmodelDescriptorById/3/0 + delete: + tags: + - Submodel Registry API + summary: "Deletes a Submodel Descriptor, i.e. de-registers a submodel" + operationId: DeleteSubmodelDescriptorById + parameters: + - name: submodelIdentifier + in: path + description: The Submodel’s unique id (UTF8-BASE64-URL-encoded) + required: true + style: simple + explode: false + schema: + type: string + format: byte + responses: + "204": + description: Submodel Descriptor deleted successfully + "400": + description: "Bad Request, e.g. the request parameters of the format of the request body is wrong." + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "404": + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + x-semanticIds: + - https://admin-shell.io/aas/API/DeleteSubmodelDescriptorById/3/0 + /description: + get: + tags: + - Description API + summary: Returns the self-describing information of a network resource (ServiceDescription) + operationId: GetDescription + responses: + "200": + description: Requested Description + content: + application/json: + schema: + $ref: '#/components/schemas/ServiceDescription' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + default: + description: Default error handling for unmentioned status codes + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + x-semanticIds: + - https://admin-shell.io/aas/API/Descriptor/GetDescription/3/0 +components: + schemas: + GetSubmodelDescriptorsResult: + allOf: + - $ref: '#/components/schemas/PagedResult' + - type: object + properties: + result: + type: array + items: + $ref: '#/components/schemas/SubmodelDescriptor' + PagedResult: + type: object + properties: + paging_metadata: + $ref: '#/components/schemas/PagedResult_paging_metadata' + SubmodelDescriptor: + required: + - endpoints + - id + allOf: + - $ref: '#/components/schemas/Descriptor' + - type: object + properties: + id: + maxLength: 2000 + minLength: 1 + pattern: "^[\\x09\\x0A\\x0D\\x20-\\uD7FF\\uE000-\\uFFFD\\u10000-\\u10FFFF]*$" + type: string + administration: + $ref: '#/components/schemas/AdministrativeInformation' + endpoints: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/Endpoint' + idShort: + maxLength: 128 + type: string + semanticId: + $ref: '#/components/schemas/Reference' + supplementalSemanticId: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/Reference' + Descriptor: + type: object + properties: + description: + type: array + items: + $ref: '#/components/schemas/LangStringTextType' + displayName: + type: array + items: + $ref: '#/components/schemas/LangStringNameType' + extensions: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/Extension' + LangStringTextType: + allOf: + - $ref: '#/components/schemas/AbstractLangString' + - required: + - text + properties: + text: + minLength: 1 + maxLength: 1023 + pattern: "^([\\t\\n\\r -\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + AbstractLangString: + required: + - language + type: object + properties: + language: + pattern: "^(([a-zA-Z]{2,3}(-[a-zA-Z]{3}(-[a-zA-Z]{3}){2})?|[a-zA-Z]{4}|[a-zA-Z]{5,8})(-[a-zA-Z]{4})?(-([a-zA-Z]{2}|[0-9]{3}))?(-(([a-zA-Z0-9]){5,8}|[0-9]([a-zA-Z0-9]){3}))*(-[0-9A-WY-Za-wy-z](-([a-zA-Z0-9]){2,8})+)*(-[xX](-([a-zA-Z0-9]){1,8})+)?|[xX](-([a-zA-Z0-9]){1,8})+|((en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)))$" + type: string + LangStringNameType: + allOf: + - $ref: '#/components/schemas/AbstractLangString' + - required: + - text + properties: + text: + minLength: 1 + maxLength: 128 + pattern: "^([\\t\\n\\r -\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + Extension: + allOf: + - $ref: '#/components/schemas/HasSemantics' + - required: + - name + properties: + name: + maxLength: 128 + minLength: 1 + pattern: "^([\\t\\n\\r -\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + valueType: + $ref: '#/components/schemas/DataTypeDefXsd' + value: + type: string + refersTo: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/Reference' + HasSemantics: + type: object + properties: + semanticId: + $ref: '#/components/schemas/Reference' + supplementalSemanticIds: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/Reference' + Reference: + allOf: + - $ref: '#/components/schemas/ReferenceParent' + - properties: + referredSemanticId: + $ref: '#/components/schemas/ReferenceParent' + ReferenceParent: + required: + - keys + - type + type: object + properties: + type: + $ref: '#/components/schemas/ReferenceTypes' + keys: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/Key' + ReferenceTypes: + type: string + enum: + - ExternalReference + - ModelReference + Key: + required: + - type + - value + type: object + properties: + type: + $ref: '#/components/schemas/KeyTypes' + value: + maxLength: 2000 + minLength: 1 + pattern: "^([\\t\\n\\r -\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + KeyTypes: + type: string + enum: + - AnnotatedRelationshipElement + - AssetAdministrationShell + - BasicEventElement + - Blob + - Capability + - ConceptDescription + - DataElement + - Entity + - EventElement + - File + - FragmentReference + - GlobalReference + - Identifiable + - MultiLanguageProperty + - Operation + - Property + - Range + - Referable + - ReferenceElement + - RelationshipElement + - Submodel + - SubmodelElement + - SubmodelElementCollection + - SubmodelElementList + DataTypeDefXsd: + type: string + enum: + - xs:anyURI + - xs:base64Binary + - xs:boolean + - xs:byte + - xs:date + - xs:dateTime + - xs:decimal + - xs:double + - xs:duration + - xs:float + - xs:gDay + - xs:gMonth + - xs:gMonthDay + - xs:gYear + - xs:gYearMonth + - xs:hexBinary + - xs:int + - xs:integer + - xs:long + - xs:negativeInteger + - xs:nonNegativeInteger + - xs:nonPositiveInteger + - xs:positiveInteger + - xs:short + - xs:string + - xs:time + - xs:unsignedByte + - xs:unsignedInt + - xs:unsignedLong + - xs:unsignedShort + AdministrativeInformation: + allOf: + - $ref: '#/components/schemas/HasDataSpecification' + - properties: + version: + type: string + pattern: "^(0|[1-9][0-9]{1,3})$" + revision: + type: string + pattern: "^(0|[1-9][0-9]{1,3})$" + creator: + $ref: '#/components/schemas/Reference' + templateId: + maxLength: 2000 + minLength: 1 + pattern: "^([\\t\\n\\r \\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + HasDataSpecification: + type: object + properties: + embeddedDataSpecifications: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/EmbeddedDataSpecification' + EmbeddedDataSpecification: + required: + - dataSpecification + - dataSpecificationContent + type: object + properties: + dataSpecification: + $ref: '#/components/schemas/Reference' + dataSpecificationContent: + $ref: '#/components/schemas/DataSpecificationContent' + DataSpecificationIec61360: + type: object + required: + - preferredName + - modelType + properties: + modelType: + default: DataSpecificationIec61360 + type: string + preferredName: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/LangStringPreferredNameTypeIec61360' + shortName: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/LangStringShortNameTypeIec61360' + unit: + minLength: 1 + pattern: "^([\\t\\n\\r -\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + unitId: + $ref: '#/components/schemas/Reference' + sourceOfDefinition: + minLength: 1 + pattern: "^([\\t\\n\\r -\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + symbol: + minLength: 1 + pattern: "^([\\t\\n\\r -\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + dataType: + $ref: '#/components/schemas/DataTypeIec61360' + definition: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/LangStringDefinitionTypeIec61360' + valueFormat: + minLength: 1 + pattern: "^([\\t\\n\\r -\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + valueList: + $ref: '#/components/schemas/ValueList' + value: + maxLength: 2000 + minLength: 1 + pattern: "^([\\t\\n\\r -\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + levelType: + $ref: '#/components/schemas/LevelType' + DataSpecificationContent: + type: object + required: + - modelType + oneOf: + - $ref: '#/components/schemas/DataSpecificationIec61360' + discriminator: + propertyName: modelType + ModelType: + type: string + enum: + - AnnotatedRelationshipElement + - AssetAdministrationShell + - BasicEventElement + - Blob + - Capability + - ConceptDescription + - DataSpecificationIec61360 + - Entity + - File + - MultiLanguageProperty + - Operation + - Property + - Range + - ReferenceElement + - RelationshipElement + - Submodel + - SubmodelElementCollection + - SubmodelElementList + LangStringPreferredNameTypeIec61360: + allOf: + - $ref: '#/components/schemas/AbstractLangString' + - required: + - text + properties: + text: + minLength: 1 + maxLength: 255 + pattern: "^([\\t\\n\\r -\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + LangStringShortNameTypeIec61360: + allOf: + - $ref: '#/components/schemas/AbstractLangString' + - required: + - text + properties: + text: + minLength: 1 + maxLength: 18 + pattern: "^([\\t\\n\\r -\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + DataTypeIec61360: + type: string + enum: + - BLOB + - BOOLEAN + - DATE + - FILE + - HTML + - INTEGER_COUNT + - INTEGER_CURRENCY + - INTEGER_MEASURE + - IRDI + - IRI + - RATIONAL + - RATIONAL_MEASURE + - REAL_COUNT + - REAL_CURRENCY + - REAL_MEASURE + - STRING + - STRING_TRANSLATABLE + - TIME + - TIMESTAMP + LangStringDefinitionTypeIec61360: + allOf: + - $ref: '#/components/schemas/AbstractLangString' + - required: + - text + properties: + text: + minLength: 1 + maxLength: 1023 + pattern: "^([\\t\\n\\r -\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + ValueList: + required: + - valueReferencePairs + type: object + properties: + valueReferencePairs: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/ValueReferencePair' + ValueReferencePair: + required: + - value + - valueId + type: object + properties: + value: + maxLength: 2000 + minLength: 1 + pattern: "^([\\t\\n\\r -\\ud7ff\\ue000-\\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + valueId: + $ref: '#/components/schemas/Reference' + LevelType: + required: + - max + - min + - nom + - typ + type: object + properties: + min: + type: boolean + nom: + type: boolean + typ: + type: boolean + max: + type: boolean + Endpoint: + required: + - interface + - protocolInformation + type: object + properties: + interface: + maxLength: 128 + type: string + protocolInformation: + $ref: '#/components/schemas/ProtocolInformation' + ProtocolInformation: + required: + - href + type: object + properties: + href: + maxLength: 2048 + type: string + endpointProtocol: + maxLength: 128 + type: string + endpointProtocolVersion: + type: array + items: + maxLength: 128 + type: string + subprotocol: + maxLength: 128 + type: string + subprotocolBody: + maxLength: 128 + type: string + subprotocolBodyEncoding: + maxLength: 128 + type: string + securityAttributes: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/ProtocolInformation_securityAttributes' + Result: + type: object + properties: + messages: + type: array + items: + $ref: '#/components/schemas/Message' + Message: + type: object + properties: + code: + maxLength: 32 + minLength: 1 + type: string + correlationId: + maxLength: 128 + minLength: 1 + type: string + messageType: + type: string + enum: + - Undefined + - Info + - Warning + - Error + - Exception + text: + type: string + timestamp: + format: date-time + type: string + ServiceDescription: + type: object + properties: + profiles: + minItems: 1 + type: array + items: + type: string + enum: + - https://admin-shell.io/aas/API/3/0/AssetAdministrationShellServiceSpecification/SSP-001 + - https://admin-shell.io/aas/API/3/0/AssetAdministrationShellServiceSpecification/SSP-002 + - https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-001 + - https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-002 + - https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003 + - https://admin-shell.io/aas/API/3/0/AasxFileServerServiceSpecification/SSP-001 + - https://admin-shell.io/aas/API/3/0/AssetAdministrationShellRegistryServiceSpecification/SSP-001 + - https://admin-shell.io/aas/API/3/0/AssetAdministrationShellRegistryServiceSpecification/SSP-002 + - https://admin-shell.io/aas/API/3/0/SubmodelRegistryServiceSpecification/SSP-001 + - https://admin-shell.io/aas/API/3/0/SubmodelRegistryServiceSpecification/SSP-002 + - https://admin-shell.io/aas/API/3/0/DiscoveryServiceSpecification/SSP-001 + - https://admin-shell.io/aas/API/3/0/AssetAdministrationShellRepositoryServiceSpecification/SSP-001 + - https://admin-shell.io/aas/API/3/0/AssetAdministrationShellRepositoryServiceSpecification/SSP-002 + - https://admin-shell.io/aas/API/3/0/SubmodelRepositoryServiceSpecification/SSP-001 + - https://admin-shell.io/aas/API/3/0/SubmodelRepositoryServiceSpecification/SSP-002 + - https://admin-shell.io/aas/API/3/0/SubmodelRepositoryServiceSpecification/SSP-003 + - https://admin-shell.io/aas/API/3/0/SubmodelRepositoryServiceSpecification/SSP-004 + - https://admin-shell.io/aas/API/3/0/ConceptDescriptionServiceSpecification/SSP-001 + description: "The Description object enables servers to present their capabilities to the clients, in particular which profiles they implement. At least one defined profile is required. Additional, proprietary attributes might be included. Nevertheless, the server must not expect that a regular client understands them." +# example: |- +# { +# "profiles": [ +# "https://admin-shell.io/aas/API/3/0/AssetAdministrationShellRegistryServiceSpecification/SSP-002", +# "https://admin-shell.io/aas/API/3/0/SubmodelRegistryServiceSpecification/SSP-002" +# ] +# } + PagedResult_paging_metadata: + type: object + properties: + cursor: + type: string +# example: wJlCDLIl6KTWypN7T6vc6nWEmEYe99Hjf1XY1xmqV-M=# + ProtocolInformation_securityAttributes: + required: + - key + - type + - value + type: object + properties: + type: + type: string + enum: + - NONE + - RFC_TLSA + - W3C_DID + key: + type: string + value: + type: string + responses: + bad-request: + description: "Bad Request, e.g. the request parameters of the format of the request body is wrong." + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + forbidden: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + not-found: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + internal-server-error: + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + default: + description: Default error handling for unmentioned status codes + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + conflict: + description: "Conflict, a resource which shall be created exists already. Might be thrown if a Submodel or SubmodelElement with the same ShortId is contained in a POST request." + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + parameters: + Limit: + name: limit + in: query + description: The maximum number of elements in the response array + required: false + schema: + minimum: 1 + type: integer + Cursor: + name: cursor + in: query + description: A server-generated identifier retrieved from pagingMetadata that specifies from which position the result listing should continue + required: false + schema: + type: string + SubmodelIdentifier: + name: submodelIdentifier + in: path + description: The Submodel’s unique id (UTF8-BASE64-URL-encoded) + required: true + style: simple + explode: false + schema: + type: string + format: byte diff --git a/basyx.submodelregistry/open-api/Plattform_i40-SubmodelRegistryServiceSpecification-V3.0.1_SSP-001-resolved.yaml b/basyx.submodelregistry/open-api/Plattform_i40-SubmodelRegistryServiceSpecification-V3.0.1_SSP-001-resolved.yaml new file mode 100644 index 000000000..756a3d9cc --- /dev/null +++ b/basyx.submodelregistry/open-api/Plattform_i40-SubmodelRegistryServiceSpecification-V3.0.1_SSP-001-resolved.yaml @@ -0,0 +1,944 @@ +--- +openapi: 3.0.3 +info: + title: DotAAS Part 2 | HTTP/REST | Submodel Registry Service Specification + description: "The Full Profile of the Submodel Registry Service Specification as part of the [Specification of the Asset Administration Shell: Part 2](http://industrialdigitaltwin.org/en/content-hub). \nPublisher: Industrial Digital Twin Association (IDTA) April 2023" + contact: + name: Industrial Digital Twin Association (IDTA) + email: info@idtwin.org + license: + name: CC BY 4.0 + url: https://creativecommons.org/licenses/by/4.0/ + version: V3.0.1_SSP-001 +servers: +- url: "{protocol}://{host_name}:{port}/api/{version_prefix}" + variables: + protocol: + description: Allows access through http and https (recommended) + default: https + enum: + - http + - https + host_name: + description: Hostname of server hosting the api + default: admin-shell.io + port: + description: "80 is default for http, 443 for https" + default: "443" + enum: + - "80" + - "443" + version_prefix: + default: v3.0 + enum: + - v3.0 +paths: + /submodel-descriptors: + get: + tags: + - Submodel Registry API + summary: Returns all Submodel Descriptors + operationId: GetAllSubmodelDescriptors + parameters: + - name: limit + in: query + description: The maximum number of elements in the response array + required: false + schema: + minimum: 1 + type: integer + - name: cursor + in: query + description: A server-generated identifier retrieved from pagingMetadata that specifies from which position the result listing should continue + required: false + schema: + type: string + responses: + "200": + description: Requested Submodel Descriptors + content: + application/json: + schema: + $ref: '#/components/schemas/GetSubmodelDescriptorsResult' + "400": + description: "Bad Request, e.g. the request parameters of the format of the request body is wrong." + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "404": + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + default: + description: Default error handling for unmentioned status codes + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + x-semanticIds: + - https://admin-shell.io/aas/API/GetAllSubmodelDescriptors/3/0 + post: + tags: + - Submodel Registry API + summary: "Creates a new Submodel Descriptor, i.e. registers a submodel" + operationId: PostSubmodelDescriptor + requestBody: + description: Submodel Descriptor object + content: + application/json: + schema: + $ref: '#/components/schemas/SubmodelDescriptor' + required: true + responses: + "201": + description: Submodel Descriptor created successfully + headers: + Location: + description: URL of the newly created resource + style: simple + explode: false + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/SubmodelDescriptor' + "400": + description: "Bad Request, e.g. the request parameters of the format of the request body is wrong." + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "409": + description: "Conflict, a resource which shall be created exists already. Might be thrown if a Submodel or SubmodelElement with the same ShortId is contained in a POST request." + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + default: + description: Default error handling for unmentioned status codes + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + x-semanticIds: + - https://admin-shell.io/aas/API/PostSubmodelDescriptor/3/0 + /submodel-descriptors/{submodelIdentifier}: + get: + tags: + - Submodel Registry API + summary: Returns a specific Submodel Descriptor + operationId: GetSubmodelDescriptorById + parameters: + - name: submodelIdentifier + in: path + description: The Submodel’s unique id (UTF8-BASE64-URL-encoded) + required: true + style: simple + explode: false + schema: + type: string + format: byte + responses: + "200": + description: Requested Submodel Descriptor + content: + application/json: + schema: + $ref: '#/components/schemas/SubmodelDescriptor' + "400": + description: "Bad Request, e.g. the request parameters of the format of the request body is wrong." + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "404": + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + default: + description: Default error handling for unmentioned status codes + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + x-semanticIds: + - https://admin-shell.io/aas/API/GetSubmodelDescriptorById/3/0 + put: + tags: + - Submodel Registry API + summary: Updates an existing Submodel Descriptor + operationId: PutSubmodelDescriptorById + parameters: + - name: submodelIdentifier + in: path + description: The Submodel’s unique id (UTF8-BASE64-URL-encoded) + required: true + style: simple + explode: false + schema: + type: string + format: byte + requestBody: + description: Submodel Descriptor object + content: + application/json: + schema: + $ref: '#/components/schemas/SubmodelDescriptor' + required: true + responses: + "204": + description: Submodel Descriptor updated successfully + "400": + description: "Bad Request, e.g. the request parameters of the format of the request body is wrong." + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "404": + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + default: + description: Default error handling for unmentioned status codes + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + x-semanticIds: + - https://admin-shell.io/aas/API/PutSubmodelDescriptorById/3/0 + delete: + tags: + - Submodel Registry API + summary: "Deletes a Submodel Descriptor, i.e. de-registers a submodel" + operationId: DeleteSubmodelDescriptorById + parameters: + - name: submodelIdentifier + in: path + description: The Submodel’s unique id (UTF8-BASE64-URL-encoded) + required: true + style: simple + explode: false + schema: + type: string + format: byte + responses: + "204": + description: Submodel Descriptor deleted successfully + "400": + description: "Bad Request, e.g. the request parameters of the format of the request body is wrong." + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "404": + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + x-semanticIds: + - https://admin-shell.io/aas/API/DeleteSubmodelDescriptorById/3/0 + /description: + get: + tags: + - Description API + summary: Returns the self-describing information of a network resource (ServiceDescription) + operationId: GetDescription + responses: + "200": + description: Requested Description + content: + application/json: + schema: + $ref: '#/components/schemas/ServiceDescription' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + default: + description: Default error handling for unmentioned status codes + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + x-semanticIds: + - https://admin-shell.io/aas/API/Descriptor/GetDescription/3/0 +components: + schemas: + GetSubmodelDescriptorsResult: + allOf: + - $ref: '#/components/schemas/PagedResult' + - type: object + properties: + result: + type: array + items: + $ref: '#/components/schemas/SubmodelDescriptor' + PagedResult: + type: object + properties: + paging_metadata: + $ref: '#/components/schemas/PagedResult_paging_metadata' + SubmodelDescriptor: + required: + - endpoints + - id + type: object + properties: + administration: + $ref: '#/components/schemas/AdministrativeInformation' + endpoints: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/Endpoint' + idShort: + maxLength: 128 + type: string + id: + maxLength: 2000 + minLength: 1 + pattern: "^[\\x09\\x0A\\x0D\\x20-\\uD7FF\\uE000-\\uFFFD\\U00010000-\\U0010FFFF]*$" + type: string + semanticId: + $ref: '#/components/schemas/Reference' + supplementalSemanticId: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/Reference' + example: "{ \"id\": \"https://admin-shell.io/zvei/nameplate/1/0/Nameplate\", \"endpoints\": [ { \"href\": { \"href\": \"https://localhost:1234/api/v3.0/submodel\", \"endpointProtocol\": \"HTTP\", \"endpointProtocolVersion\": [\"1.1\"] }, \"interface\": \"AAS-3.0\" }, { \"protocolInformation\": { \"href\": \"opc.tcp://localhost:4840\" }, \"interface\": \"AAS-3.0\" }, { \"protocolInformation\": { \"href\": \"https://localhost:5678\", \"endpointProtocol\": \"HTTP\", \"endpointProtocolVersion\": [\"1.1\"], \"subprotocol\": \"OPC UA Basic SOAP\", \"subprotocolBody\": \"ns=2;s=MyAAS\", \"subprotocolBodyEncoding\": \"application/soap+xml\" }, \"interface\": \"AAS-3.0\" } ] }" + allOf: + - $ref: '#/components/schemas/Descriptor' + Descriptor: + type: object + properties: + description: + type: array + items: + $ref: '#/components/schemas/LangStringTextType' + displayName: + type: array + items: + $ref: '#/components/schemas/LangStringNameType' + extensions: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/Extension' + example: "{ \"endpoints\": [ { \"protocolInformation\": { \"href\": \"https://localhost:1234/api/v3.0/aas\", \"endpointProtocolVersion\": [\"1.1\"] }, \"interface\": \"AAS-3.0\" }, { \"protocolInformation\": { \"href\": \"opc.tcp://localhost:4840\" }, \"interface\": \"AAS-3.0\" }, { \"protocolInformation\": { \"href\": \"https://localhost:5678\", \"endpointProtocolVersion\": [\"1.1\"], \"subprotocol\": \"OPC UA Basic SOAP\", \"subprotocolBody\": \"ns=2;s=MyAAS\", \"subprotocolBodyEncoding\": \"application/soap+xml\" }, \"interface\": \"AAS-3.0\" } ] }" + LangStringTextType: + allOf: + - $ref: '#/components/schemas/AbstractLangString' + - properties: + text: + maxLength: 1023 + AbstractLangString: + required: + - language + - text + type: object + properties: + language: + pattern: "^(([a-zA-Z]{2,3}(-[a-zA-Z]{3}(-[a-zA-Z]{3}){2})?|[a-zA-Z]{4}|[a-zA-Z]{5,8})(-[a-zA-Z]{4})?(-([a-zA-Z]{2}|[0-9]{3}))?(-(([a-zA-Z0-9]){5,8}|[0-9]([a-zA-Z0-9]){3}))*(-[0-9A-WY-Za-wy-z](-([a-zA-Z0-9]){2,8})+)*(-[xX](-([a-zA-Z0-9]){1,8})+)?|[xX](-([a-zA-Z0-9]){1,8})+|((en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)))$" + type: string + text: + minLength: 1 + pattern: "^([\\t\\n\\r -퟿-�]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + LangStringNameType: + allOf: + - $ref: '#/components/schemas/AbstractLangString' + - properties: + text: + maxLength: 128 + Extension: + allOf: + - $ref: '#/components/schemas/HasSemantics' + - required: + - name + properties: + name: + maxLength: 128 + minLength: 1 + pattern: "^([\\t\\n\\r -퟿-�]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + valueType: + $ref: '#/components/schemas/DataTypeDefXsd' + value: + type: string + refersTo: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/Reference' + HasSemantics: + type: object + properties: + semanticId: + $ref: '#/components/schemas/Reference' + supplementalSemanticIds: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/Reference' + Reference: + allOf: + - $ref: '#/components/schemas/ReferenceParent' + - properties: + referredSemanticId: + $ref: '#/components/schemas/ReferenceParent' + ReferenceParent: + required: + - keys + - type + type: object + properties: + type: + $ref: '#/components/schemas/ReferenceTypes' + keys: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/Key' + ReferenceTypes: + type: string + enum: + - ExternalReference + - ModelReference + Key: + required: + - type + - value + type: object + properties: + type: + $ref: '#/components/schemas/KeyTypes' + value: + maxLength: 2000 + minLength: 1 + pattern: "^([\\t\\n\\r -퟿-�]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + KeyTypes: + type: string + enum: + - AnnotatedRelationshipElement + - AssetAdministrationShell + - BasicEventElement + - Blob + - Capability + - ConceptDescription + - DataElement + - Entity + - EventElement + - File + - FragmentReference + - GlobalReference + - Identifiable + - MultiLanguageProperty + - Operation + - Property + - Range + - Referable + - ReferenceElement + - RelationshipElement + - Submodel + - SubmodelElement + - SubmodelElementCollection + - SubmodelElementList + DataTypeDefXsd: + type: string + enum: + - xs:anyURI + - xs:base64Binary + - xs:boolean + - xs:byte + - xs:date + - xs:dateTime + - xs:decimal + - xs:double + - xs:duration + - xs:float + - xs:gDay + - xs:gMonth + - xs:gMonthDay + - xs:gYear + - xs:gYearMonth + - xs:hexBinary + - xs:int + - xs:integer + - xs:long + - xs:negativeInteger + - xs:nonNegativeInteger + - xs:nonPositiveInteger + - xs:positiveInteger + - xs:short + - xs:string + - xs:time + - xs:unsignedByte + - xs:unsignedInt + - xs:unsignedLong + - xs:unsignedShort + AdministrativeInformation: + allOf: + - $ref: '#/components/schemas/HasDataSpecification' + - properties: + version: + type: string + allOf: + - maxLength: 4 + minLength: 1 + - pattern: "^([\\t\\n\\r -퟿-�]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + - pattern: "^(0|[1-9][0-9]*)$" + revision: + type: string + allOf: + - maxLength: 4 + minLength: 1 + - pattern: "^([\\t\\n\\r -퟿-�]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + - pattern: "^(0|[1-9][0-9]*)$" + creator: + $ref: '#/components/schemas/Reference' + templateId: + maxLength: 2000 + minLength: 1 + pattern: "^([\\t\\n\\r -퟿-�]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + HasDataSpecification: + type: object + properties: + embeddedDataSpecifications: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/EmbeddedDataSpecification' + EmbeddedDataSpecification: + required: + - dataSpecification + - dataSpecificationContent + type: object + properties: + dataSpecification: + $ref: '#/components/schemas/Reference' + dataSpecificationContent: + $ref: '#/components/schemas/DataSpecificationContent_choice' + DataSpecificationContent_choice: + oneOf: + - $ref: '#/components/schemas/DataSpecificationIec61360' + DataSpecificationIec61360: + allOf: + - $ref: '#/components/schemas/DataSpecificationContent' + - required: + - preferredName + properties: + preferredName: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/LangStringPreferredNameTypeIec61360' + shortName: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/LangStringShortNameTypeIec61360' + unit: + minLength: 1 + pattern: "^([\\t\\n\\r -퟿-�]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + unitId: + $ref: '#/components/schemas/Reference' + sourceOfDefinition: + minLength: 1 + pattern: "^([\\t\\n\\r -퟿-�]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + symbol: + minLength: 1 + pattern: "^([\\t\\n\\r -퟿-�]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + dataType: + $ref: '#/components/schemas/DataTypeIec61360' + definition: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/LangStringDefinitionTypeIec61360' + valueFormat: + minLength: 1 + pattern: "^([\\t\\n\\r -퟿-�]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + valueList: + $ref: '#/components/schemas/ValueList' + value: + maxLength: 2000 + minLength: 1 + pattern: "^([\\t\\n\\r -퟿-�]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + levelType: + $ref: '#/components/schemas/LevelType' + modelType: + pattern: DataSpecificationIec61360 + type: string + DataSpecificationContent: + required: + - modelType + type: object + properties: + modelType: + $ref: '#/components/schemas/ModelType' + ModelType: + type: string + enum: + - AnnotatedRelationshipElement + - AssetAdministrationShell + - BasicEventElement + - Blob + - Capability + - ConceptDescription + - DataSpecificationIec61360 + - Entity + - File + - MultiLanguageProperty + - Operation + - Property + - Range + - ReferenceElement + - RelationshipElement + - Submodel + - SubmodelElementCollection + - SubmodelElementList + LangStringPreferredNameTypeIec61360: + allOf: + - $ref: '#/components/schemas/AbstractLangString' + - properties: + text: + maxLength: 255 + LangStringShortNameTypeIec61360: + allOf: + - $ref: '#/components/schemas/AbstractLangString' + - properties: + text: + maxLength: 18 + DataTypeIec61360: + type: string + enum: + - BLOB + - BOOLEAN + - DATE + - FILE + - HTML + - INTEGER_COUNT + - INTEGER_CURRENCY + - INTEGER_MEASURE + - IRDI + - IRI + - RATIONAL + - RATIONAL_MEASURE + - REAL_COUNT + - REAL_CURRENCY + - REAL_MEASURE + - STRING + - STRING_TRANSLATABLE + - TIME + - TIMESTAMP + LangStringDefinitionTypeIec61360: + allOf: + - $ref: '#/components/schemas/AbstractLangString' + - properties: + text: + maxLength: 1023 + ValueList: + required: + - valueReferencePairs + type: object + properties: + valueReferencePairs: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/ValueReferencePair' + ValueReferencePair: + required: + - value + - valueId + type: object + properties: + value: + maxLength: 2000 + minLength: 1 + pattern: "^([\\t\\n\\r -퟿-�]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + type: string + valueId: + $ref: '#/components/schemas/Reference' + LevelType: + required: + - max + - min + - nom + - typ + type: object + properties: + min: + type: boolean + nom: + type: boolean + typ: + type: boolean + max: + type: boolean + Endpoint: + required: + - interface + - protocolInformation + type: object + properties: + interface: + maxLength: 128 + type: string + protocolInformation: + $ref: '#/components/schemas/ProtocolInformation' + ProtocolInformation: + required: + - href + type: object + properties: + href: + maxLength: 2048 + type: string + endpointProtocol: + maxLength: 128 + type: string + endpointProtocolVersion: + type: array + items: + maxLength: 128 + type: string + subprotocol: + maxLength: 128 + type: string + subprotocolBody: + maxLength: 128 + type: string + subprotocolBodyEncoding: + maxLength: 128 + type: string + securityAttributes: + minItems: 1 + type: array + items: + $ref: '#/components/schemas/ProtocolInformation_securityAttributes' + Result: + type: object + properties: + messages: + type: array + items: + $ref: '#/components/schemas/Message' + Message: + type: object + properties: + code: + maxLength: 32 + minLength: 1 + type: string + correlationId: + maxLength: 128 + minLength: 1 + type: string + messageType: + type: string + enum: + - Undefined + - Info + - Warning + - Error + - Exception + text: + type: string + timestamp: + pattern: "^-?(([1-9][0-9][0-9][0-9]+)|(0[0-9][0-9][0-9]))-((0[1-9])|(1[0-2]))-((0[1-9])|([12][0-9])|(3[01]))T(((([01][0-9])|(2[0-3])):[0-5][0-9]:([0-5][0-9])(\\.[0-9]+)?)|24:00:00(\\.0+)?)(Z|\\+00:00|-00:00)$" + type: string + ServiceDescription: + type: object + properties: + profiles: + minItems: 1 + type: array + items: + type: string + enum: + - https://admin-shell.io/aas/API/3/0/AssetAdministrationShellServiceSpecification/SSP-001 + - https://admin-shell.io/aas/API/3/0/AssetAdministrationShellServiceSpecification/SSP-002 + - https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-001 + - https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-002 + - https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003 + - https://admin-shell.io/aas/API/3/0/AasxFileServerServiceSpecification/SSP-001 + - https://admin-shell.io/aas/API/3/0/AssetAdministrationShellRegistryServiceSpecification/SSP-001 + - https://admin-shell.io/aas/API/3/0/AssetAdministrationShellRegistryServiceSpecification/SSP-002 + - https://admin-shell.io/aas/API/3/0/SubmodelRegistryServiceSpecification/SSP-001 + - https://admin-shell.io/aas/API/3/0/SubmodelRegistryServiceSpecification/SSP-002 + - https://admin-shell.io/aas/API/3/0/DiscoveryServiceSpecification/SSP-001 + - https://admin-shell.io/aas/API/3/0/AssetAdministrationShellRepositoryServiceSpecification/SSP-001 + - https://admin-shell.io/aas/API/3/0/AssetAdministrationShellRepositoryServiceSpecification/SSP-002 + - https://admin-shell.io/aas/API/3/0/SubmodelRepositoryServiceSpecification/SSP-001 + - https://admin-shell.io/aas/API/3/0/SubmodelRepositoryServiceSpecification/SSP-002 + - https://admin-shell.io/aas/API/3/0/SubmodelRepositoryServiceSpecification/SSP-003 + - https://admin-shell.io/aas/API/3/0/SubmodelRepositoryServiceSpecification/SSP-004 + - https://admin-shell.io/aas/API/3/0/ConceptDescriptionServiceSpecification/SSP-001 + description: "The Description object enables servers to present their capabilities to the clients, in particular which profiles they implement. At least one defined profile is required. Additional, proprietary attributes might be included. Nevertheless, the server must not expect that a regular client understands them." + example: |- + { + "profiles": [ + "https://admin-shell.io/aas/API/3/0/AssetAdministrationShellRegistryServiceSpecification/SSP-002", + "https://admin-shell.io/aas/API/3/0/SubmodelRegistryServiceSpecification/SSP-002" + ] + } + PagedResult_paging_metadata: + type: object + properties: + cursor: + type: string + example: wJlCDLIl6KTWypN7T6vc6nWEmEYe99Hjf1XY1xmqV-M=# + ProtocolInformation_securityAttributes: + required: + - key + - type + - value + type: object + properties: + type: + type: string + enum: + - NONE + - RFC_TLSA + - W3C_DID + key: + type: string + value: + type: string + responses: + bad-request: + description: "Bad Request, e.g. the request parameters of the format of the request body is wrong." + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + forbidden: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + not-found: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + internal-server-error: + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + default: + description: Default error handling for unmentioned status codes + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + conflict: + description: "Conflict, a resource which shall be created exists already. Might be thrown if a Submodel or SubmodelElement with the same ShortId is contained in a POST request." + content: + application/json: + schema: + $ref: '#/components/schemas/Result' + parameters: + Limit: + name: limit + in: query + description: The maximum number of elements in the response array + required: false + schema: + minimum: 1 + type: integer + Cursor: + name: cursor + in: query + description: A server-generated identifier retrieved from pagingMetadata that specifies from which position the result listing should continue + required: false + schema: + type: string + SubmodelIdentifier: + name: submodelIdentifier + in: path + description: The Submodel’s unique id (UTF8-BASE64-URL-encoded) + required: true + style: simple + explode: false + schema: + type: string + format: byte diff --git a/basyx.submodelregistry/open-api/patch-base-extensions.yaml b/basyx.submodelregistry/open-api/patch-base-extensions.yaml new file mode 100644 index 000000000..73566d0dc --- /dev/null +++ b/basyx.submodelregistry/open-api/patch-base-extensions.yaml @@ -0,0 +1,54 @@ +- op: replace + path: /servers + value: [] +- op: add + path: /paths/~1submodel-descriptors~1{submodelIdentifier}/get/parameters/0/x-utf8-base64-url-encoded-as-string + value: true +- op: add + path: /paths/~1submodel-descriptors~1{submodelIdentifier}/put/parameters/0/x-utf8-base64-url-encoded-as-string + value: true +- op: add + path: /paths/~1submodel-descriptors~1{submodelIdentifier}/delete/parameters/0/x-utf8-base64-url-encoded-as-string + value: true +- op: add + path: /paths/~1submodel-descriptors/delete + value: + tags: + - Submodel Registry + summary: Deletes all Submodel Descriptors + operationId: DeleteAllSubmodelDescriptors + responses: + '204': + description: No content +- op: add + path: /paths/~1description/get/tags + value: + - Description +- op: replace + path: /paths/~1submodel-descriptors/get/tags + value: + - Submodel Registry +- op: replace + path: /paths/~1submodel-descriptors/delete/tags + value: + - Submodel Registry +- op: replace + path: /paths/~1submodel-descriptors/post/tags + value: + - Submodel Registry +- op: replace + path: /paths/~1submodel-descriptors~1{submodelIdentifier}/get/tags + value: + - Submodel Registry +- op: replace + path: /paths/~1submodel-descriptors~1{submodelIdentifier}/put/tags + value: + - Submodel Registry +- op: replace + path: /paths/~1submodel-descriptors~1{submodelIdentifier}/delete/tags + value: + - Submodel Registry +- op: replace + path: /paths/~1description/get/tags + value: + - Submodel Registry \ No newline at end of file diff --git a/basyx.submodelregistry/pom.xml b/basyx.submodelregistry/pom.xml new file mode 100644 index 000000000..270bbb891 --- /dev/null +++ b/basyx.submodelregistry/pom.xml @@ -0,0 +1,343 @@ + + + 4.0.0 + basyx.submodelregistry + pom + + + org.eclipse.digitaltwin.basyx + basyx.parent + ${revision} + + + + 2.0.0-SNAPSHOT + 1.18.1 + 1.18.20.0 + 11 + ${java.version} + ${java.version} + UTF-8 + src/generated + 3.0.42 + 3.0.0 + open-api + Plattform_i40-SubmodelRegistryServiceSpecification-V3.0.1_SSP-001-resolved-altered.yaml + Plattform_i40-SubmodelRegistry-and-Discovery.yaml + 31.0.1-jre + 3.6.0 + 3.0-alpha-2 + 0.9.4 + 1.30 + 0.40.1 + ${project.artifactId} + docker.io + linux/amd64,linux/arm64 + docker.io + 6.6.0 + 0.2.6 + 3.0.2 + 0.5.0 + patch-base-extensions.yaml + + + + + basyx.submodelregistry-service-basemodel + basyx.submodelregistry-service + basyx.submodelregistry-service-inmemory-storage + basyx.submodelregistry-service-release-log-mem + basyx.submodelregistry-client-native + basyx.submodelregistry-service-mongodb-storage + basyx.submodelregistry-service-kafka-events + basyx.submodelregistry-service-basetests + basyx.submodelregistry-service-release-kafka-mem + basyx.submodelregistry-service-release-log-mongodb + basyx.submodelregistry-service-release-kafka-mongodb + + + + + Gerhard Sonnenberg + gerhard.sonnenberg@dfki.de + DFKI GmbH + https://www.dfki.de/en/web + + developer + + + + + + + + org.projectlombok + lombok-maven-plugin + ${lombok.maven-plugin.version} + + + io.swagger.codegen.v3 + swagger-codegen-maven-plugin + ${swagger.codegen.version} + + + + org.openapitools + openapi-generator-maven-plugin + ${openapitools.version} + + + io.fabric8 + docker-maven-plugin + ${docker.maven.plugin.version} + + + de.dfki.cos.basys.common + jsonpatch-maven-plugin + ${jsonpatch.plugin.version} + + + + + + + + com.google.code.findbugs + jsr305 + ${jsr305.version} + + + org.openapitools + openapi-generator + ${openapitools.version} + + + org.testcontainers + mongodb + ${testcontainers.version} + test + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-kafka-events + ${project.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service + ${project.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-mongodb-storage + ${project.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-basemodel + ${project.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-client-native + ${project.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-inmemory-storage + ${project.version} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-basetests + test + ${project.version} + + + io.springfox + springfox-oas + ${spring.fox.version} + + + io.springfox + springfox-swagger-ui + ${spring.fox.version} + + + org.openapitools + jackson-databind-nullable + ${openapitools.jacksonnullable.version} + + + com.google.guava + guava + ${guava.version} + + + org.apache.maven + maven-plugin-api + ${maven-plugin.version} + + + org.apache.maven.plugin-tools + maven-plugin-annotations + ${maven-plugin.version} + + + org.apache.maven + maven-project + ${maven-project.version} + + + org.apache.maven.plugins + maven-plugin-plugin + ${maven-plugin.version} + + + com.github.spullara.mustache.java + compiler + ${mustache.compiler.version} + + + + + + delombok + + + src/main/lombok + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + generate-sources + + add-source + + + + src/main/generated + + + + + + + org.projectlombok + lombok-maven-plugin + + + generate-sources + + delombok + + + + + + + + + dockerbuild + + + + src/main/docker/Dockerfile + + + docker.username + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + true + true + + + + + + repackage + + + + + + io.fabric8 + docker-maven-plugin + + + build-docker + package + + build + + + + + ${docker.username}/${docker.image.name} + + + artifact + + + ${docker.image.version} + + Dockerfile + ${project.basedir}/src/main/docker + + + + + + + + push-docker + deploy + + build + push + + + + + ${docker.username}/${docker.image.name} + + https://index.docker.io/v1/ + + + artifact + + + ${docker.image.version} + + Dockerfile + ${project.basedir}/src/main/docker + + ${docker.target.platforms} + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 2c6ecd6d2..62a147656 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,7 @@ basyx.common basyx.submodelservice basyx.submodelrepository + basyx.submodelregistry basyx.aasservice basyx.aasrepository basyx.aasregistry