From 4029fcf64bd21ec03579efa88d1e68c8c9351bdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20J=C3=A4ckle?= Date: Tue, 20 Jun 2023 16:31:56 +0200 Subject: [PATCH] #1650 provide WoT TM validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * restructuring of "ditto-wot" module to enable re-usability of non-pekko/non-Ditto specifics * adding "validator" concept and first sample implementation (WIP) Signed-off-by: Thomas Jäckle --- .../SubscribeForPersistedEvents.java | 5 - .../service/config/http/HttpProxyConfig.java | 95 +---- .../http/DefaultHttpProxyConfigTest.java | 32 +- bom/pom.xml | 31 +- connectivity/service/pom.xml | 4 + gateway/service/pom.xml | 4 + internal/utils/cache-loaders/pom.xml | 5 + internal/utils/config/pom.xml | 12 - .../http/DefaultHttpProxyBaseConfig.java | 125 +++++++ .../config/http/HttpProxyBaseConfig.java | 111 ++++++ .../http/DefaultHttpProxyBaseConfigTest.java | 94 +++++ .../src/test/resources/http-proxy-test.conf | 9 + .../utils/http/DefaultHttpClientFacade.java | 12 +- internal/utils/protocol/pom.xml | 5 + .../cbor/JacksonSerializationContext.java | 2 +- things/service/pom.xml | 4 + .../common/config/DittoThingsConfig.java | 4 +- .../service/common/config/ThingsConfig.java | 2 +- .../AbstractThingCommandStrategy.java | 15 +- .../commands/CreateThingStrategy.java | 11 +- .../commands/DeleteAttributeStrategy.java | 13 +- .../commands/DeleteAttributesStrategy.java | 11 +- .../DeleteFeatureDefinitionStrategy.java | 11 +- ...eleteFeatureDesiredPropertiesStrategy.java | 9 +- .../DeleteFeatureDesiredPropertyStrategy.java | 13 +- .../DeleteFeaturePropertiesStrategy.java | 11 +- .../DeleteFeaturePropertyStrategy.java | 13 +- .../commands/DeleteFeatureStrategy.java | 11 +- .../commands/DeleteFeaturesStrategy.java | 11 +- .../DeleteThingDefinitionStrategy.java | 11 +- .../commands/DeleteThingStrategy.java | 11 +- .../commands/MergeThingStrategy.java | 7 +- .../commands/ModifyAttributeStrategy.java | 17 +- .../commands/ModifyAttributesStrategy.java | 13 +- .../ModifyFeatureDefinitionStrategy.java | 11 +- ...odifyFeatureDesiredPropertiesStrategy.java | 13 +- .../ModifyFeatureDesiredPropertyStrategy.java | 17 +- .../ModifyFeaturePropertiesStrategy.java | 13 +- .../ModifyFeaturePropertyStrategy.java | 17 +- .../commands/ModifyFeatureStrategy.java | 71 ++-- .../commands/ModifyFeaturesStrategy.java | 13 +- .../commands/ModifyPolicyIdStrategy.java | 7 +- .../ModifyThingDefinitionStrategy.java | 11 +- .../commands/ModifyThingStrategy.java | 36 +- .../commands/RetrieveAttributeStrategy.java | 15 +- .../commands/RetrieveAttributesStrategy.java | 13 +- .../RetrieveFeatureDefinitionStrategy.java | 11 +- ...rieveFeatureDesiredPropertiesStrategy.java | 13 +- ...etrieveFeatureDesiredPropertyStrategy.java | 15 +- .../RetrieveFeaturePropertiesStrategy.java | 13 +- .../RetrieveFeaturePropertyStrategy.java | 15 +- .../commands/RetrieveFeatureStrategy.java | 8 +- .../commands/RetrieveFeaturesStrategy.java | 7 +- .../commands/RetrievePolicyIdStrategy.java | 7 +- .../RetrieveThingDefinitionStrategy.java | 9 +- .../commands/RetrieveThingStrategy.java | 8 +- .../commands/SudoRetrieveThingStrategy.java | 7 +- .../commands/ThingCommandStrategies.java | 96 ++--- .../commands/ThingConflictStrategy.java | 7 +- things/service/src/main/resources/things.conf | 30 ++ .../commands/DeleteAttributeStrategyTest.java | 6 +- .../DeleteAttributesStrategyTest.java | 6 +- .../DeleteFeatureDefinitionStrategyTest.java | 6 +- ...eFeatureDesiredPropertiesStrategyTest.java | 6 +- ...eteFeatureDesiredPropertyStrategyTest.java | 6 +- .../DeleteFeaturePropertiesStrategyTest.java | 6 +- .../DeleteFeaturePropertyStrategyTest.java | 6 +- .../commands/DeleteFeatureStrategyTest.java | 7 +- .../commands/DeleteFeaturesStrategyTest.java | 6 +- .../DeleteThingDefinitionStrategyTest.java | 6 +- .../commands/DeleteThingStrategyTest.java | 6 +- .../commands/MergeThingStrategyTest.java | 6 +- .../commands/ModifyAttributeStrategyTest.java | 6 +- .../ModifyAttributesStrategyTest.java | 8 +- .../ModifyFeatureDefinitionStrategyTest.java | 6 +- ...yFeatureDesiredPropertiesStrategyTest.java | 6 +- ...ifyFeatureDesiredPropertyStrategyTest.java | 7 +- .../ModifyFeaturePropertiesStrategyTest.java | 6 +- .../ModifyFeaturePropertyStrategyTest.java | 7 +- .../commands/ModifyFeatureStrategyTest.java | 7 +- .../commands/ModifyFeaturesStrategyTest.java | 7 +- .../commands/ModifyPolicyIdStrategyTest.java | 6 +- .../ModifyThingDefinitionStrategyTest.java | 6 +- .../commands/ModifyThingStrategyTest.java | 6 +- .../RetrieveAttributeStrategyTest.java | 6 +- .../RetrieveAttributesStrategyTest.java | 6 +- ...RetrieveFeatureDefinitionStrategyTest.java | 6 +- ...eFeatureDesiredPropertiesStrategyTest.java | 6 +- ...eveFeatureDesiredPropertyStrategyTest.java | 6 +- ...RetrieveFeaturePropertiesStrategyTest.java | 6 +- .../RetrieveFeaturePropertyStrategyTest.java | 6 +- .../commands/RetrieveFeatureStrategyTest.java | 7 +- .../RetrieveFeaturesStrategyTest.java | 6 +- .../RetrievePolicyIdStrategyTest.java | 6 +- .../RetrieveThingDefinitionStrategyTest.java | 6 +- .../commands/RetrieveThingStrategyTest.java | 7 +- .../SudoRetrieveThingStrategyTest.java | 6 +- .../commands/ThingConflictStrategyTest.java | 9 +- wot/api/pom.xml | 56 +++ .../DefaultFeatureValidationConfig.java | 148 ++++++++ .../config/DefaultThingValidationConfig.java | 127 +++++++ .../config/DefaultTmBasedCreationConfig.java | 4 +- .../config/DefaultTmScopedCreationConfig.java | 4 +- .../api/config/DefaultTmValidationConfig.java | 99 ++++++ .../DefaultToThingDescriptionConfig.java | 4 +- .../wot/api}/config/DefaultWotConfig.java | 35 +- .../api}/config/TmBasedCreationConfig.java | 4 +- .../api}/config/TmScopedCreationConfig.java | 4 +- .../api}/config/ToThingDescriptionConfig.java | 4 +- .../ditto/wot/api}/config/WotConfig.java | 18 +- .../ditto/wot/api}/config/package-info.java | 6 +- .../DefaultWotThingDescriptionGenerator.java | 172 +++++++-- ...DefaultWotThingModelExtensionResolver.java | 9 +- .../DefaultWotThingSkeletonGenerator.java | 182 ++++++++-- .../wot/api}/generator/DittoWotExtension.java | 4 +- .../WotThingDescriptionGenerator.java} | 88 +++-- .../WotThingModelExtensionResolver.java | 19 +- .../generator/WotThingSkeletonGenerator.java | 59 +++- .../wot/api}/generator/package-info.java | 6 +- .../provider/DefaultWotThingModelFetcher.java | 112 ++++++ .../wot/api/provider/JsonDownloader.java | 38 ++ .../api}/provider/WotThingModelFetcher.java | 24 +- .../ditto/wot/api}/provider/package-info.java | 6 +- .../DefaultWotThingModelResolver.java | 114 ++++++ .../api/resolver/WotThingModelResolver.java | 82 +++++ .../ditto/wot/api/resolver/package-info.java | 20 ++ .../DefaultWotThingModelValidator.java | 126 +++++++ .../api/validator/WotThingModelValidator.java | 65 ++++ .../ditto/wot/api/validator/package-info.java | 18 + wot/integration/pom.xml | 14 +- .../DefaultDittoWotIntegration.java | 134 +++++++ .../wot/integration/DittoWotIntegration.java | 80 +++++ ...cher.java => PekkoHttpJsonDownloader.java} | 119 ++----- .../WotThingDescriptionGenerator.java | 80 ----- .../ditto/wot/integration/package-info.java | 19 + .../DefaultWotThingDescriptionProvider.java | 332 ------------------ wot/model/pom.xml | 1 + wot/pom.xml | 4 +- wot/validation/pom.xml | 67 ++++ .../DefaultWotThingModelValidation.java | 216 ++++++++++++ .../wot/validation/JsonSchemaExtractor.java | 42 +++ ...tThingModelPayloadValidationException.java | 243 +++++++++++++ .../validation/WotThingModelValidation.java | 49 +++ .../config/FeatureValidationConfig.java | 110 ++++++ .../config/ThingValidationConfig.java | 96 +++++ .../validation/config/TmValidationConfig.java | 75 ++++ .../ditto/wot/validation/package-info.java | 18 + 147 files changed, 3682 insertions(+), 1141 deletions(-) create mode 100644 internal/utils/config/src/main/java/org/eclipse/ditto/internal/utils/config/http/DefaultHttpProxyBaseConfig.java create mode 100644 internal/utils/config/src/main/java/org/eclipse/ditto/internal/utils/config/http/HttpProxyBaseConfig.java create mode 100644 internal/utils/config/src/test/java/org/eclipse/ditto/internal/utils/config/http/DefaultHttpProxyBaseConfigTest.java create mode 100644 internal/utils/config/src/test/resources/http-proxy-test.conf create mode 100755 wot/api/pom.xml create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultFeatureValidationConfig.java create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultThingValidationConfig.java rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/config/DefaultTmBasedCreationConfig.java (96%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/config/DefaultTmScopedCreationConfig.java (96%) create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultTmValidationConfig.java rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/config/DefaultToThingDescriptionConfig.java (97%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/config/DefaultWotConfig.java (74%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/config/TmBasedCreationConfig.java (94%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/config/TmScopedCreationConfig.java (96%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/config/ToThingDescriptionConfig.java (96%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/config/WotConfig.java (70%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/config/package-info.java (80%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/generator/DefaultWotThingDescriptionGenerator.java (86%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/generator/DefaultWotThingModelExtensionResolver.java (97%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/generator/DefaultWotThingSkeletonGenerator.java (74%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/generator/DittoWotExtension.java (91%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration/provider/WotThingDescriptionProvider.java => api/src/main/java/org/eclipse/ditto/wot/api/generator/WotThingDescriptionGenerator.java} (50%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/generator/WotThingModelExtensionResolver.java (87%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/generator/WotThingSkeletonGenerator.java (69%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/generator/package-info.java (79%) create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/DefaultWotThingModelFetcher.java create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/JsonDownloader.java rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/provider/WotThingModelFetcher.java (73%) rename wot/{integration/src/main/java/org/eclipse/ditto/wot/integration => api/src/main/java/org/eclipse/ditto/wot/api}/provider/package-info.java (80%) create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/DefaultWotThingModelResolver.java create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/WotThingModelResolver.java create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/package-info.java create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/DefaultWotThingModelValidator.java create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/WotThingModelValidator.java create mode 100644 wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/package-info.java create mode 100644 wot/integration/src/main/java/org/eclipse/ditto/wot/integration/DefaultDittoWotIntegration.java create mode 100644 wot/integration/src/main/java/org/eclipse/ditto/wot/integration/DittoWotIntegration.java rename wot/integration/src/main/java/org/eclipse/ditto/wot/integration/{provider/DefaultWotThingModelFetcher.java => PekkoHttpJsonDownloader.java} (56%) delete mode 100644 wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/WotThingDescriptionGenerator.java create mode 100644 wot/integration/src/main/java/org/eclipse/ditto/wot/integration/package-info.java delete mode 100644 wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/DefaultWotThingDescriptionProvider.java create mode 100644 wot/validation/pom.xml create mode 100644 wot/validation/src/main/java/org/eclipse/ditto/wot/validation/DefaultWotThingModelValidation.java create mode 100644 wot/validation/src/main/java/org/eclipse/ditto/wot/validation/JsonSchemaExtractor.java create mode 100644 wot/validation/src/main/java/org/eclipse/ditto/wot/validation/WotThingModelPayloadValidationException.java create mode 100644 wot/validation/src/main/java/org/eclipse/ditto/wot/validation/WotThingModelValidation.java create mode 100644 wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/FeatureValidationConfig.java create mode 100644 wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/ThingValidationConfig.java create mode 100644 wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/TmValidationConfig.java create mode 100644 wot/validation/src/main/java/org/eclipse/ditto/wot/validation/package-info.java diff --git a/base/model/src/main/java/org/eclipse/ditto/base/model/signals/commands/streaming/SubscribeForPersistedEvents.java b/base/model/src/main/java/org/eclipse/ditto/base/model/signals/commands/streaming/SubscribeForPersistedEvents.java index d41b6f7d570..9ee3dd0cc25 100755 --- a/base/model/src/main/java/org/eclipse/ditto/base/model/signals/commands/streaming/SubscribeForPersistedEvents.java +++ b/base/model/src/main/java/org/eclipse/ditto/base/model/signals/commands/streaming/SubscribeForPersistedEvents.java @@ -384,11 +384,6 @@ protected void appendPayload(final JsonObjectBuilder jsonObjectBuilder, getFilter().ifPresent(theFilter -> jsonObjectBuilder.set(JsonFields.FILTER, theFilter)); } - @Override - public String getTypePrefix() { - return TYPE_PREFIX; - } - @Override public SubscribeForPersistedEvents setDittoHeaders(final DittoHeaders dittoHeaders) { return new SubscribeForPersistedEvents(entityId, resourcePath, fromHistoricalRevision, toHistoricalRevision, diff --git a/base/service/src/main/java/org/eclipse/ditto/base/service/config/http/HttpProxyConfig.java b/base/service/src/main/java/org/eclipse/ditto/base/service/config/http/HttpProxyConfig.java index 8a67baed539..31479585ee6 100644 --- a/base/service/src/main/java/org/eclipse/ditto/base/service/config/http/HttpProxyConfig.java +++ b/base/service/src/main/java/org/eclipse/ditto/base/service/config/http/HttpProxyConfig.java @@ -14,108 +14,21 @@ import javax.annotation.concurrent.Immutable; -import org.eclipse.ditto.internal.utils.config.KnownConfigValue; - import org.apache.pekko.http.javadsl.ClientTransport; +import org.eclipse.ditto.internal.utils.config.http.HttpProxyBaseConfig; /** * Provides configuration settings for the HTTP proxy. */ @Immutable -public interface HttpProxyConfig { - - /** - * Indicates whether the HTTP proxy should be enabled. - * - * @return {@code true} if the HTTP proxy should be enabled, {@code false} else. - */ - boolean isEnabled(); - - /** - * Returns the host name of the HTTP proxy. - * - * @return the host name - */ - String getHostname(); - - /** - * Returns the port of the HTTP proxy. - * - * @return the port. - */ - int getPort(); - - /** - * Returns the user name of the HTTP proxy. - * - * @return the user name. - */ - String getUsername(); +public interface HttpProxyConfig extends HttpProxyBaseConfig { /** - * Returns the password of the HTTP proxy. - * - * @return the password. - */ - String getPassword(); - - /** - * Converts the proxy settings to an Pekko HTTP client transport object. + * Converts the proxy settings to a Pekko HTTP client transport object. * Does not check whether the proxy is enabled. * - * @return an Pekko HTTP client transport object matching this config. + * @return a Pekko HTTP client transport object matching this config. */ ClientTransport toClientTransport(); - /** - * An enumeration of the known config path expressions and their associated default values for - * {@code HttpProxyConfig}. - */ - enum HttpProxyConfigValue implements KnownConfigValue { - - /** - * Determines whether the HTTP proxy should be enabled. - */ - ENABLED("enabled", false), - - /** - * The host name of the HTTP proxy. - */ - HOST_NAME("hostname", ""), - - /** - * The port of the HTTP proxy. - */ - PORT("port", 0), - - /** - * The user name of the HTTP proxy. - */ - USER_NAME("username", ""), - - /** - * The password of the HTTP proxy. - */ - PASSWORD("password", ""); - - private final String path; - private final Object defaultValue; - - private HttpProxyConfigValue(final String thePath, final Object theDefaultValue) { - path = thePath; - defaultValue = theDefaultValue; - } - - @Override - public Object getDefaultValue() { - return defaultValue; - } - - @Override - public String getConfigPath() { - return path; - } - - } - } diff --git a/base/service/src/test/java/org/eclipse/ditto/base/service/config/http/DefaultHttpProxyConfigTest.java b/base/service/src/test/java/org/eclipse/ditto/base/service/config/http/DefaultHttpProxyConfigTest.java index ee956c49971..a425a833cc7 100644 --- a/base/service/src/test/java/org/eclipse/ditto/base/service/config/http/DefaultHttpProxyConfigTest.java +++ b/base/service/src/test/java/org/eclipse/ditto/base/service/config/http/DefaultHttpProxyConfigTest.java @@ -16,7 +16,7 @@ import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable; import org.assertj.core.api.JUnitSoftAssertions; -import org.eclipse.ditto.base.service.config.http.HttpProxyConfig.HttpProxyConfigValue; +import org.eclipse.ditto.internal.utils.config.http.HttpProxyBaseConfig; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; @@ -58,20 +58,20 @@ public void underTestReturnsDefaultValuesIfBaseConfigWasEmpty() { final DefaultHttpProxyConfig underTest = DefaultHttpProxyConfig.ofHttpProxy(ConfigFactory.empty()); softly.assertThat(underTest.isEnabled()) - .as(HttpProxyConfigValue.ENABLED.getConfigPath()) - .isEqualTo(HttpProxyConfigValue.ENABLED.getDefaultValue()); + .as(HttpProxyBaseConfig.HttpProxyConfigValue.ENABLED.getConfigPath()) + .isEqualTo(HttpProxyBaseConfig.HttpProxyConfigValue.ENABLED.getDefaultValue()); softly.assertThat(underTest.getHostname()) - .as(HttpProxyConfigValue.HOST_NAME.getConfigPath()) - .isEqualTo(HttpProxyConfigValue.HOST_NAME.getDefaultValue()); + .as(HttpProxyBaseConfig.HttpProxyConfigValue.HOST_NAME.getConfigPath()) + .isEqualTo(HttpProxyBaseConfig.HttpProxyConfigValue.HOST_NAME.getDefaultValue()); softly.assertThat(underTest.getPort()) - .as(HttpProxyConfigValue.PORT.getConfigPath()) - .isEqualTo(HttpProxyConfigValue.PORT.getDefaultValue()); + .as(HttpProxyBaseConfig.HttpProxyConfigValue.PORT.getConfigPath()) + .isEqualTo(HttpProxyBaseConfig.HttpProxyConfigValue.PORT.getDefaultValue()); softly.assertThat(underTest.getUsername()) - .as(HttpProxyConfigValue.USER_NAME.getConfigPath()) - .isEqualTo(HttpProxyConfigValue.USER_NAME.getDefaultValue()); + .as(HttpProxyBaseConfig.HttpProxyConfigValue.USER_NAME.getConfigPath()) + .isEqualTo(HttpProxyBaseConfig.HttpProxyConfigValue.USER_NAME.getDefaultValue()); softly.assertThat(underTest.getPassword()) - .as(HttpProxyConfigValue.PASSWORD.getConfigPath()) - .isEqualTo(HttpProxyConfigValue.PASSWORD.getDefaultValue()); + .as(HttpProxyBaseConfig.HttpProxyConfigValue.PASSWORD.getConfigPath()) + .isEqualTo(HttpProxyBaseConfig.HttpProxyConfigValue.PASSWORD.getDefaultValue()); } @Test @@ -79,19 +79,19 @@ public void underTestReturnsValuesOfConfigFile() { final DefaultHttpProxyConfig underTest = DefaultHttpProxyConfig.ofHttpProxy(httpProxyConfig); softly.assertThat(underTest.isEnabled()) - .as(HttpProxyConfigValue.ENABLED.getConfigPath()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.ENABLED.getConfigPath()) .isTrue(); softly.assertThat(underTest.getHostname()) - .as(HttpProxyConfigValue.HOST_NAME.getConfigPath()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.HOST_NAME.getConfigPath()) .isEqualTo("example.com"); softly.assertThat(underTest.getPort()) - .as(HttpProxyConfigValue.PORT.getConfigPath()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.PORT.getConfigPath()) .isEqualTo(4711); softly.assertThat(underTest.getUsername()) - .as(HttpProxyConfigValue.USER_NAME.getConfigPath()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.USER_NAME.getConfigPath()) .isEqualTo("john.frume"); softly.assertThat(underTest.getPassword()) - .as(HttpProxyConfigValue.PASSWORD.getConfigPath()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.PASSWORD.getConfigPath()) .isEqualTo("verySecretPW!"); } diff --git a/bom/pom.xml b/bom/pom.xml index 218f0cf1950..a17197d51bf 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -27,7 +27,7 @@ 2.13 - 2.13.12 + 2.13.14 1.1.2 1.0.2 @@ -37,6 +37,7 @@ 0.9.5 2.16.1 + 1.4.0 1.4.3 0.6.1 3.6.1 @@ -75,7 +76,7 @@ 3.1.11 - 2.7.0 + 2.7.1 3.0.2 @@ -130,6 +131,22 @@ import + + com.networknt + json-schema-validator + ${json-schema-validator.version} + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + + + org.apache.commons + commons-lang3 + + + + com.typesafe config @@ -538,11 +555,21 @@ ditto-rql-query ${project.version} + + org.eclipse.ditto + ditto-wot-api + ${project.version} + org.eclipse.ditto ditto-wot-model ${project.version} + + org.eclipse.ditto + ditto-wot-validation + ${project.version} + org.eclipse.ditto ditto-wot-integration diff --git a/connectivity/service/pom.xml b/connectivity/service/pom.xml index fe4984a3cf0..6f34b876510 100644 --- a/connectivity/service/pom.xml +++ b/connectivity/service/pom.xml @@ -55,6 +55,10 @@ org.eclipse.ditto ditto-wot-model + + org.eclipse.ditto + ditto-wot-validation + org.eclipse.ditto diff --git a/gateway/service/pom.xml b/gateway/service/pom.xml index bdb9d688095..3bd8277820d 100644 --- a/gateway/service/pom.xml +++ b/gateway/service/pom.xml @@ -101,6 +101,10 @@ org.eclipse.ditto ditto-wot-model + + org.eclipse.ditto + ditto-wot-validation + org.eclipse.ditto ditto-internal-models-signalenrichment diff --git a/internal/utils/cache-loaders/pom.xml b/internal/utils/cache-loaders/pom.xml index ccca6698434..866f78907f0 100644 --- a/internal/utils/cache-loaders/pom.xml +++ b/internal/utils/cache-loaders/pom.xml @@ -49,6 +49,11 @@ logback-classic test + + org.apache.pekko + pekko-slf4j_${scala.version} + test + org.apache.pekko pekko-testkit_${scala.version} diff --git a/internal/utils/config/pom.xml b/internal/utils/config/pom.xml index aebbdf6b16c..02078c21484 100755 --- a/internal/utils/config/pom.xml +++ b/internal/utils/config/pom.xml @@ -46,18 +46,6 @@ org.slf4j slf4j-api - - org.apache.pekko - pekko-actor_${scala.version} - - - org.apache.pekko - pekko-lease-kubernetes_${scala.version} - - - org.apache.pekko - pekko-coordination_${scala.version} - diff --git a/internal/utils/config/src/main/java/org/eclipse/ditto/internal/utils/config/http/DefaultHttpProxyBaseConfig.java b/internal/utils/config/src/main/java/org/eclipse/ditto/internal/utils/config/http/DefaultHttpProxyBaseConfig.java new file mode 100644 index 00000000000..5ee63c22a2b --- /dev/null +++ b/internal/utils/config/src/main/java/org/eclipse/ditto/internal/utils/config/http/DefaultHttpProxyBaseConfig.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.internal.utils.config.http; + +import java.util.Objects; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +import org.eclipse.ditto.internal.utils.config.ConfigWithFallback; + +import com.typesafe.config.Config; + +/** + * This class is the default implementation of the HTTP proxy config. + */ +@Immutable +public final class DefaultHttpProxyBaseConfig implements HttpProxyBaseConfig { + + private static final String HTTP_PROXY_PATH = "http.proxy"; + private static final String PROXY_PATH = "proxy"; + + private final boolean enabled; + private final String hostName; + private final int port; + private final String userName; + private final String password; + + private DefaultHttpProxyBaseConfig(final ConfigWithFallback configWithFallback) { + enabled = configWithFallback.getBoolean(HttpProxyConfigValue.ENABLED.getConfigPath()); + hostName = configWithFallback.getString(HttpProxyConfigValue.HOST_NAME.getConfigPath()); + port = configWithFallback.getInt(HttpProxyConfigValue.PORT.getConfigPath()); + userName = configWithFallback.getString(HttpProxyConfigValue.USER_NAME.getConfigPath()); + password = configWithFallback.getString(HttpProxyConfigValue.PASSWORD.getConfigPath()); + } + + /** + * Returns an instance of {@code DefaultHttpProxyConfig} based on the settings of the specified Config. + * + * @param config is supposed to provide the settings of the HTTP proxy config at {@value #HTTP_PROXY_PATH}. + * @return the instance. + * @throws org.eclipse.ditto.internal.utils.config.DittoConfigError if {@code config} is invalid. + */ + public static DefaultHttpProxyBaseConfig ofHttpProxy(final Config config) { + return ofConfigPath(config, HTTP_PROXY_PATH); + } + + public static DefaultHttpProxyBaseConfig ofProxy(final Config config) { + return ofConfigPath(config, PROXY_PATH); + } + + private static DefaultHttpProxyBaseConfig ofConfigPath(final Config config, final String relativePath) { + return new DefaultHttpProxyBaseConfig( + ConfigWithFallback.newInstance(config, relativePath, HttpProxyConfigValue.values())); + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public String getHostname() { + return hostName; + } + + @Override + public int getPort() { + return port; + } + + @Override + public String getUsername() { + return userName; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public boolean equals(@Nullable final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final DefaultHttpProxyBaseConfig that = (DefaultHttpProxyBaseConfig) o; + return enabled == that.enabled && + port == that.port && + Objects.equals(hostName, that.hostName) && + Objects.equals(userName, that.userName) && + Objects.equals(password, that.password); + } + + @Override + public int hashCode() { + return Objects.hash(enabled, hostName, port, userName, password); + } + + @SuppressWarnings("squid:S2068") + @Override + public String toString() { + return getClass().getSimpleName() + " [" + + "enabled=" + enabled + + ", hostName=" + hostName + + ", port=" + port + + ", userName=" + userName + + ", password=*****" + + "]"; + } + +} diff --git a/internal/utils/config/src/main/java/org/eclipse/ditto/internal/utils/config/http/HttpProxyBaseConfig.java b/internal/utils/config/src/main/java/org/eclipse/ditto/internal/utils/config/http/HttpProxyBaseConfig.java new file mode 100644 index 00000000000..93af767e595 --- /dev/null +++ b/internal/utils/config/src/main/java/org/eclipse/ditto/internal/utils/config/http/HttpProxyBaseConfig.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.internal.utils.config.http; + +import javax.annotation.concurrent.Immutable; + +import org.eclipse.ditto.internal.utils.config.KnownConfigValue; + +/** + * Provides configuration settings for the HTTP proxy. + */ +@Immutable +public interface HttpProxyBaseConfig { + + /** + * Indicates whether the HTTP proxy should be enabled. + * + * @return {@code true} if the HTTP proxy should be enabled, {@code false} else. + */ + boolean isEnabled(); + + /** + * Returns the host name of the HTTP proxy. + * + * @return the host name + */ + String getHostname(); + + /** + * Returns the port of the HTTP proxy. + * + * @return the port. + */ + int getPort(); + + /** + * Returns the user name of the HTTP proxy. + * + * @return the user name. + */ + String getUsername(); + + /** + * Returns the password of the HTTP proxy. + * + * @return the password. + */ + String getPassword(); + + /** + * An enumeration of the known config path expressions and their associated default values for + * {@code HttpProxyConfig}. + */ + enum HttpProxyConfigValue implements KnownConfigValue { + + /** + * Determines whether the HTTP proxy should be enabled. + */ + ENABLED("enabled", false), + + /** + * The host name of the HTTP proxy. + */ + HOST_NAME("hostname", ""), + + /** + * The port of the HTTP proxy. + */ + PORT("port", 0), + + /** + * The user name of the HTTP proxy. + */ + USER_NAME("username", ""), + + /** + * The password of the HTTP proxy. + */ + PASSWORD("password", ""); + + private final String path; + private final Object defaultValue; + + private HttpProxyConfigValue(final String thePath, final Object theDefaultValue) { + path = thePath; + defaultValue = theDefaultValue; + } + + @Override + public Object getDefaultValue() { + return defaultValue; + } + + @Override + public String getConfigPath() { + return path; + } + + } + +} diff --git a/internal/utils/config/src/test/java/org/eclipse/ditto/internal/utils/config/http/DefaultHttpProxyBaseConfigTest.java b/internal/utils/config/src/test/java/org/eclipse/ditto/internal/utils/config/http/DefaultHttpProxyBaseConfigTest.java new file mode 100644 index 00000000000..c34a4c0590f --- /dev/null +++ b/internal/utils/config/src/test/java/org/eclipse/ditto/internal/utils/config/http/DefaultHttpProxyBaseConfigTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.internal.utils.config.http; + +import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf; +import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable; + +import org.assertj.core.api.JUnitSoftAssertions; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; + +import junit.framework.TestCase; +import nl.jqno.equalsverifier.EqualsVerifier; + +public class DefaultHttpProxyBaseConfigTest extends TestCase { + + private static Config httpProxyConfig; + + @Rule + public final JUnitSoftAssertions softly = new JUnitSoftAssertions(); + + @BeforeClass + public static void initTestFixture() { + httpProxyConfig = ConfigFactory.load("http-proxy-test"); + } + + @Test + public void assertImmutability() { + assertInstancesOf(DefaultHttpProxyBaseConfig.class, areImmutable()); + } + + @Test + public void testHashCodeAndEquals() { + EqualsVerifier.forClass(DefaultHttpProxyBaseConfig.class) + .usingGetClass() + .verify(); + } + + @Test + public void underTestReturnsDefaultValuesIfBaseConfigWasEmpty() { + final DefaultHttpProxyBaseConfig underTest = DefaultHttpProxyBaseConfig.ofHttpProxy(ConfigFactory.empty()); + + softly.assertThat(underTest.isEnabled()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.ENABLED.getConfigPath()) + .isEqualTo(HttpProxyBaseConfig.HttpProxyConfigValue.ENABLED.getDefaultValue()); + softly.assertThat(underTest.getHostname()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.HOST_NAME.getConfigPath()) + .isEqualTo(HttpProxyBaseConfig.HttpProxyConfigValue.HOST_NAME.getDefaultValue()); + softly.assertThat(underTest.getPort()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.PORT.getConfigPath()) + .isEqualTo(HttpProxyBaseConfig.HttpProxyConfigValue.PORT.getDefaultValue()); + softly.assertThat(underTest.getUsername()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.USER_NAME.getConfigPath()) + .isEqualTo(HttpProxyBaseConfig.HttpProxyConfigValue.USER_NAME.getDefaultValue()); + softly.assertThat(underTest.getPassword()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.PASSWORD.getConfigPath()) + .isEqualTo(HttpProxyBaseConfig.HttpProxyConfigValue.PASSWORD.getDefaultValue()); + } + + @Test + public void underTestReturnsValuesOfConfigFile() { + final DefaultHttpProxyBaseConfig underTest = DefaultHttpProxyBaseConfig.ofHttpProxy(httpProxyConfig); + + softly.assertThat(underTest.isEnabled()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.ENABLED.getConfigPath()) + .isTrue(); + softly.assertThat(underTest.getHostname()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.HOST_NAME.getConfigPath()) + .isEqualTo("example.com"); + softly.assertThat(underTest.getPort()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.PORT.getConfigPath()) + .isEqualTo(4711); + softly.assertThat(underTest.getUsername()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.USER_NAME.getConfigPath()) + .isEqualTo("john.frume"); + softly.assertThat(underTest.getPassword()) + .as(HttpProxyBaseConfig.HttpProxyConfigValue.PASSWORD.getConfigPath()) + .isEqualTo("verySecretPW!"); + } +} \ No newline at end of file diff --git a/internal/utils/config/src/test/resources/http-proxy-test.conf b/internal/utils/config/src/test/resources/http-proxy-test.conf new file mode 100644 index 00000000000..2d51e8eeb74 --- /dev/null +++ b/internal/utils/config/src/test/resources/http-proxy-test.conf @@ -0,0 +1,9 @@ +http { + proxy { + enabled = true + hostname = "example.com" + port = 4711 + username = "john.frume" + password = "verySecretPW!" + } +} \ No newline at end of file diff --git a/internal/utils/http/src/main/java/org/eclipse/ditto/internal/utils/http/DefaultHttpClientFacade.java b/internal/utils/http/src/main/java/org/eclipse/ditto/internal/utils/http/DefaultHttpClientFacade.java index b0719d9787c..8b6e6bd3343 100644 --- a/internal/utils/http/src/main/java/org/eclipse/ditto/internal/utils/http/DefaultHttpClientFacade.java +++ b/internal/utils/http/src/main/java/org/eclipse/ditto/internal/utils/http/DefaultHttpClientFacade.java @@ -16,13 +16,13 @@ import javax.annotation.Nullable; -import org.eclipse.ditto.base.service.config.http.HttpProxyConfig; - import org.apache.pekko.actor.ActorSystem; import org.apache.pekko.http.javadsl.Http; import org.apache.pekko.http.javadsl.model.HttpRequest; import org.apache.pekko.http.javadsl.model.HttpResponse; import org.apache.pekko.http.javadsl.settings.ConnectionPoolSettings; +import org.eclipse.ditto.base.service.config.http.HttpProxyConfig; +import org.eclipse.ditto.internal.utils.config.http.HttpProxyBaseConfig; /** * Default implementation of {@link HttpClientFacade}. @@ -49,7 +49,7 @@ private DefaultHttpClientFacade(final ActorSystem actorSystem, * @return the instance. */ public static DefaultHttpClientFacade getInstance(final ActorSystem actorSystem, - final HttpProxyConfig httpProxyConfig) { + final HttpProxyBaseConfig httpProxyConfig) { // the HttpClientProvider is only configured at the very first invocation of getInstance(Config) as we can // assume that the config does not change during runtime @@ -62,10 +62,10 @@ public static DefaultHttpClientFacade getInstance(final ActorSystem actorSystem, } private static DefaultHttpClientFacade createInstance(final ActorSystem actorSystem, - final HttpProxyConfig proxyConfig) { + final HttpProxyBaseConfig proxyConfig) { ConnectionPoolSettings connectionPoolSettings = ConnectionPoolSettings.create(actorSystem); - if (proxyConfig.isEnabled()) { - connectionPoolSettings = connectionPoolSettings.withTransport(proxyConfig.toClientTransport()); + if (proxyConfig.isEnabled() && proxyConfig instanceof HttpProxyConfig pekkoHttpProxyConfig) { + connectionPoolSettings = connectionPoolSettings.withTransport(pekkoHttpProxyConfig.toClientTransport()); } return new DefaultHttpClientFacade(actorSystem, connectionPoolSettings); } diff --git a/internal/utils/protocol/pom.xml b/internal/utils/protocol/pom.xml index 56ba2d96ba4..536975e372d 100755 --- a/internal/utils/protocol/pom.xml +++ b/internal/utils/protocol/pom.xml @@ -38,6 +38,11 @@ com.typesafe config + + + org.apache.pekko + pekko-actor_${scala.version} + diff --git a/json-cbor/src/main/java/org/eclipse/ditto/json/cbor/JacksonSerializationContext.java b/json-cbor/src/main/java/org/eclipse/ditto/json/cbor/JacksonSerializationContext.java index 853509dbeb5..0ef662ee9b9 100644 --- a/json-cbor/src/main/java/org/eclipse/ditto/json/cbor/JacksonSerializationContext.java +++ b/json-cbor/src/main/java/org/eclipse/ditto/json/cbor/JacksonSerializationContext.java @@ -24,7 +24,7 @@ /** * Implementation of {@link SerializationContext} backed by Jackson's {@link JsonGenerator}. */ -final class JacksonSerializationContext implements SerializationContext { +public final class JacksonSerializationContext implements SerializationContext { private final JsonGenerator jacksonGenerator; private final ControllableOutputStream outputStream; diff --git a/things/service/pom.xml b/things/service/pom.xml index 49013ab40cc..0383a0c0072 100644 --- a/things/service/pom.xml +++ b/things/service/pom.xml @@ -83,6 +83,10 @@ org.eclipse.ditto ditto-wot-model + + org.eclipse.ditto + ditto-wot-validation + org.eclipse.ditto ditto-wot-integration diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/common/config/DittoThingsConfig.java b/things/service/src/main/java/org/eclipse/ditto/things/service/common/config/DittoThingsConfig.java index 7570d5bcc6b..611ea6ee930 100644 --- a/things/service/src/main/java/org/eclipse/ditto/things/service/common/config/DittoThingsConfig.java +++ b/things/service/src/main/java/org/eclipse/ditto/things/service/common/config/DittoThingsConfig.java @@ -30,8 +30,8 @@ import org.eclipse.ditto.internal.utils.persistence.operations.DefaultPersistenceOperationsConfig; import org.eclipse.ditto.internal.utils.persistence.operations.PersistenceOperationsConfig; import org.eclipse.ditto.internal.utils.tracing.config.TracingConfig; -import org.eclipse.ditto.wot.integration.config.DefaultWotConfig; -import org.eclipse.ditto.wot.integration.config.WotConfig; +import org.eclipse.ditto.wot.api.config.DefaultWotConfig; +import org.eclipse.ditto.wot.api.config.WotConfig; /** * This class implements the config of the Ditto Things service. diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/common/config/ThingsConfig.java b/things/service/src/main/java/org/eclipse/ditto/things/service/common/config/ThingsConfig.java index 0e26ac4b463..327e942eeb8 100644 --- a/things/service/src/main/java/org/eclipse/ditto/things/service/common/config/ThingsConfig.java +++ b/things/service/src/main/java/org/eclipse/ditto/things/service/common/config/ThingsConfig.java @@ -19,7 +19,7 @@ import org.eclipse.ditto.internal.utils.health.config.WithHealthCheckConfig; import org.eclipse.ditto.internal.utils.persistence.mongo.config.WithMongoDbConfig; import org.eclipse.ditto.internal.utils.persistence.operations.WithPersistenceOperationsConfig; -import org.eclipse.ditto.wot.integration.config.WotConfig; +import org.eclipse.ditto.wot.api.config.WotConfig; /** * Provides the configuration settings of the Things service. diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/AbstractThingCommandStrategy.java b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/AbstractThingCommandStrategy.java index ea04c8b4f0d..67360f2d98a 100644 --- a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/AbstractThingCommandStrategy.java +++ b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/AbstractThingCommandStrategy.java @@ -21,6 +21,7 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import org.apache.pekko.actor.ActorSystem; import org.eclipse.ditto.base.model.entity.metadata.Metadata; import org.eclipse.ditto.base.model.entity.metadata.MetadataBuilder; import org.eclipse.ditto.base.model.entity.metadata.MetadataModelFactory; @@ -45,6 +46,10 @@ import org.eclipse.ditto.things.model.signals.commands.modify.ThingModifyCommand; import org.eclipse.ditto.things.model.signals.commands.query.ThingQueryCommand; import org.eclipse.ditto.things.model.signals.events.ThingEvent; +import org.eclipse.ditto.wot.api.generator.WotThingDescriptionGenerator; +import org.eclipse.ditto.wot.api.generator.WotThingSkeletonGenerator; +import org.eclipse.ditto.wot.api.validator.WotThingModelValidator; +import org.eclipse.ditto.wot.integration.DittoWotIntegration; /** * Abstract base class for {@link org.eclipse.ditto.things.model.signals.commands.ThingCommand} strategies. @@ -59,8 +64,16 @@ abstract class AbstractThingCommandStrategy> private static final ConditionalHeadersValidator VALIDATOR = ThingsConditionalHeadersValidatorProvider.getInstance(); - protected AbstractThingCommandStrategy(final Class theMatchingClass) { + protected final WotThingDescriptionGenerator wotThingDescriptionGenerator; + protected final WotThingSkeletonGenerator wotThingSkeletonGenerator; + protected final WotThingModelValidator wotThingModelValidator; + + protected AbstractThingCommandStrategy(final Class theMatchingClass, final ActorSystem actorSystem) { super(theMatchingClass); + final DittoWotIntegration wotIntegration = DittoWotIntegration.get(actorSystem); + wotThingDescriptionGenerator = wotIntegration.getWotThingDescriptionGenerator(); + wotThingSkeletonGenerator = wotIntegration.getWotThingSkeletonGenerator(); + wotThingModelValidator = wotIntegration.getWotThingModelValidator(); } @Override diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/CreateThingStrategy.java b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/CreateThingStrategy.java index 7f34c850ca9..a9ad37378f2 100644 --- a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/CreateThingStrategy.java +++ b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/CreateThingStrategy.java @@ -23,6 +23,7 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import org.apache.pekko.actor.ActorSystem; import org.eclipse.ditto.base.model.entity.metadata.Metadata; import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException; import org.eclipse.ditto.base.model.headers.DittoHeaders; @@ -42,9 +43,6 @@ import org.eclipse.ditto.things.model.signals.commands.modify.CreateThingResponse; import org.eclipse.ditto.things.model.signals.events.ThingCreated; import org.eclipse.ditto.things.model.signals.events.ThingEvent; -import org.eclipse.ditto.wot.integration.provider.WotThingDescriptionProvider; - -import org.apache.pekko.actor.ActorSystem; /** * This strategy handles the {@link CreateThingStrategy} command. @@ -52,16 +50,13 @@ @Immutable final class CreateThingStrategy extends AbstractThingCommandStrategy { - private final WotThingDescriptionProvider wotThingDescriptionProvider; - /** * Constructs a new {@link CreateThingStrategy} object. * * @param actorSystem the actor system to use for loading the WoT extension. */ CreateThingStrategy(final ActorSystem actorSystem) { - super(CreateThing.class); - wotThingDescriptionProvider = WotThingDescriptionProvider.get(actorSystem); + super(CreateThing.class, actorSystem); } @Override @@ -110,7 +105,7 @@ protected Result> doApply(final Context context, final Instant now = Instant.now(); final Thing finalNewThing = newThing; - final CompletionStage thingStage = wotThingDescriptionProvider.provideThingSkeletonForCreation( + final CompletionStage thingStage = wotThingSkeletonGenerator.provideThingSkeletonForCreation( command.getEntityId(), newThing.getDefinition().orElse(null), commandHeaders diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteAttributeStrategy.java b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteAttributeStrategy.java index 8a1f6d98911..f7f11bc1258 100644 --- a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteAttributeStrategy.java +++ b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/DeleteAttributeStrategy.java @@ -17,16 +17,17 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; -import org.eclipse.ditto.json.JsonPointer; +import org.apache.pekko.actor.ActorSystem; import org.eclipse.ditto.base.model.entity.metadata.Metadata; import org.eclipse.ditto.base.model.headers.DittoHeaders; import org.eclipse.ditto.base.model.headers.WithDittoHeaders; import org.eclipse.ditto.base.model.headers.entitytag.EntityTag; +import org.eclipse.ditto.internal.utils.persistentactors.results.Result; +import org.eclipse.ditto.internal.utils.persistentactors.results.ResultFactory; +import org.eclipse.ditto.json.JsonPointer; import org.eclipse.ditto.things.model.Attributes; import org.eclipse.ditto.things.model.Thing; import org.eclipse.ditto.things.model.ThingId; -import org.eclipse.ditto.internal.utils.persistentactors.results.Result; -import org.eclipse.ditto.internal.utils.persistentactors.results.ResultFactory; import org.eclipse.ditto.things.model.signals.commands.modify.DeleteAttribute; import org.eclipse.ditto.things.model.signals.commands.modify.DeleteAttributeResponse; import org.eclipse.ditto.things.model.signals.events.AttributeDeleted; @@ -40,9 +41,11 @@ final class DeleteAttributeStrategy extends AbstractThingCommandStrategy /** * Constructs a new {@code MergeThingStrategy} object. + * + * @param actorSystem the actor system to use for loading the WoT extension. */ - MergeThingStrategy() { - super(MergeThing.class); + MergeThingStrategy(final ActorSystem actorSystem) { + super(MergeThing.class, actorSystem); } @Override diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyAttributeStrategy.java b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyAttributeStrategy.java index 4baca49012e..dc38fba537a 100644 --- a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyAttributeStrategy.java +++ b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyAttributeStrategy.java @@ -17,17 +17,18 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; -import org.eclipse.ditto.json.JsonObject; -import org.eclipse.ditto.json.JsonPointer; -import org.eclipse.ditto.json.JsonValue; +import org.apache.pekko.actor.ActorSystem; import org.eclipse.ditto.base.model.entity.metadata.Metadata; import org.eclipse.ditto.base.model.headers.DittoHeaders; import org.eclipse.ditto.base.model.headers.WithDittoHeaders; import org.eclipse.ditto.base.model.headers.entitytag.EntityTag; -import org.eclipse.ditto.things.model.Thing; -import org.eclipse.ditto.things.model.ThingId; import org.eclipse.ditto.internal.utils.persistentactors.results.Result; import org.eclipse.ditto.internal.utils.persistentactors.results.ResultFactory; +import org.eclipse.ditto.json.JsonObject; +import org.eclipse.ditto.json.JsonPointer; +import org.eclipse.ditto.json.JsonValue; +import org.eclipse.ditto.things.model.Thing; +import org.eclipse.ditto.things.model.ThingId; import org.eclipse.ditto.things.model.signals.commands.ThingCommandSizeValidator; import org.eclipse.ditto.things.model.signals.commands.modify.ModifyAttribute; import org.eclipse.ditto.things.model.signals.commands.modify.ModifyAttributeResponse; @@ -43,9 +44,11 @@ final class ModifyAttributeStrategy extends AbstractThingCommandStrategy { - private final WotThingDescriptionProvider wotThingDescriptionProvider; - /** * Constructs a new {@code ModifyFeatureStrategy} object. * * @param actorSystem the actor system to use for loading the WoT extension. */ ModifyFeatureStrategy(final ActorSystem actorSystem) { - super(ModifyFeature.class); - wotThingDescriptionProvider = WotThingDescriptionProvider.get(actorSystem); + super(ModifyFeature.class, actorSystem); } @Override @@ -93,7 +90,17 @@ protected Result> doApply(final Context context, command::getDittoHeaders); return extractFeature(command, nonNullThing) - .map(feature -> getModifyResult(context, nextRevision, command, thing, metadata)) + .map(feature -> { + final Optional featureDefinition = feature.getDefinition(); + // validate based on potentially referenced Feature WoT TM + final CompletionStage validatedStage; + if (featureDefinition.isPresent()) { + validatedStage = wotThingModelValidator.validateFeature(feature, command.getDittoHeaders()); + } else { + validatedStage = CompletableFuture.completedStage(null); + } + return getModifyResult(context, nextRevision, command, thing, metadata, validatedStage); + }) .orElseGet(() -> getCreateResult(context, nextRevision, command, thing, metadata)); } @@ -103,18 +110,24 @@ private Optional extractFeature(final ModifyFeature command, @Nullable } private Result> getModifyResult(final Context context, final long nextRevision, - final ModifyFeature command, @Nullable final Thing thing, @Nullable final Metadata metadata) { + final ModifyFeature command, @Nullable final Thing thing, @Nullable final Metadata metadata, + final CompletionStage validatedStage) { final DittoHeaders dittoHeaders = command.getDittoHeaders(); - final ThingEvent event = - FeatureModified.of(command.getEntityId(), command.getFeature(), nextRevision, getEventTimestamp(), - dittoHeaders, metadata); - final WithDittoHeaders response = appendETagHeaderIfProvided(command, - ModifyFeatureResponse.modified(context.getState(), command.getFeatureId(), dittoHeaders), - thing); - - return ResultFactory.newMutationResult(command, event, response); + final CompletionStage> eventStage = + validatedStage.thenApply(aVoid -> + FeatureModified.of(command.getEntityId(), command.getFeature(), nextRevision, + getEventTimestamp(), + dittoHeaders, metadata)); + final CompletionStage responseStage = + validatedStage.thenApply(aVoid -> + appendETagHeaderIfProvided(command, + ModifyFeatureResponse.modified(context.getState(), command.getFeatureId(), + dittoHeaders), + thing)); + + return ResultFactory.newMutationResult(command, eventStage, responseStage); } private Result> getCreateResult(final Context context, final long nextRevision, @@ -123,7 +136,7 @@ private Result> getCreateResult(final Context context, fi final DittoHeaders dittoHeaders = command.getDittoHeaders(); final Feature finalNewFeature = command.getFeature(); - final CompletionStage featureStage = wotThingDescriptionProvider.provideFeatureSkeletonForCreation( + final CompletionStage featureStage = wotThingSkeletonGenerator.provideFeatureSkeletonForCreation( finalNewFeature.getId(), finalNewFeature.getDefinition().orElse(null), dittoHeaders @@ -155,15 +168,21 @@ private Result> getCreateResult(final Context context, fi .orElse(finalNewFeature) ); + final Function> validationFunction = feature -> + wotThingModelValidator.validateFeature(feature, command.getDittoHeaders()) + .thenApply(aVoid -> feature); final CompletionStage> eventStage = - featureStage.thenApply(feature -> FeatureCreated.of(command.getEntityId(), feature, nextRevision, - getEventTimestamp(), dittoHeaders, - metadata)); - - final CompletionStage response = featureStage.thenApply(modFeature -> - appendETagHeaderIfProvided(command, - ModifyFeatureResponse.created(context.getState(), modFeature, dittoHeaders), thing) - ); + featureStage.thenCompose(validationFunction).thenApply(feature -> + FeatureCreated.of(command.getEntityId(), feature, nextRevision, + getEventTimestamp(), dittoHeaders, + metadata) + ); + + final CompletionStage response = featureStage.thenCompose(validationFunction) + .thenApply(modFeature -> + appendETagHeaderIfProvided(command, + ModifyFeatureResponse.created(context.getState(), modFeature, dittoHeaders), thing) + ); return ResultFactory.newMutationResult(command, eventStage, response); } diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeaturesStrategy.java b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeaturesStrategy.java index eb60ba43094..d08ac878b5b 100644 --- a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeaturesStrategy.java +++ b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyFeaturesStrategy.java @@ -25,6 +25,7 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import org.apache.pekko.actor.ActorSystem; import org.eclipse.ditto.base.model.entity.metadata.Metadata; import org.eclipse.ditto.base.model.headers.DittoHeaders; import org.eclipse.ditto.base.model.headers.WithDittoHeaders; @@ -47,9 +48,6 @@ import org.eclipse.ditto.things.model.signals.events.FeaturesCreated; import org.eclipse.ditto.things.model.signals.events.FeaturesModified; import org.eclipse.ditto.things.model.signals.events.ThingEvent; -import org.eclipse.ditto.wot.integration.provider.WotThingDescriptionProvider; - -import org.apache.pekko.actor.ActorSystem; /** * This strategy handles the {@link org.eclipse.ditto.things.model.signals.commands.modify.ModifyFeatures} command. @@ -57,16 +55,13 @@ @Immutable final class ModifyFeaturesStrategy extends AbstractThingCommandStrategy { - private final WotThingDescriptionProvider wotThingDescriptionProvider; - /** * Constructs a new {@code ModifyFeaturesStrategy} object. * * @param actorSystem the actor system to use for loading the WoT extension. */ ModifyFeaturesStrategy(final ActorSystem actorSystem) { - super(ModifyFeatures.class); - wotThingDescriptionProvider = WotThingDescriptionProvider.get(actorSystem); + super(ModifyFeatures.class, actorSystem); } @Override @@ -120,7 +115,7 @@ private Result> getCreateResult(final Context context, fi final List> featureStages = command.getFeatures() .stream() - .map(feature -> wotThingDescriptionProvider.provideFeatureSkeletonForCreation( + .map(feature -> wotThingSkeletonGenerator.provideFeatureSkeletonForCreation( feature.getId(), feature.getDefinition().orElse(null), dittoHeaders @@ -153,7 +148,7 @@ private Result> getCreateResult(final Context context, fi .toList(); final CompletableFuture featuresStage = - CompletableFuture.allOf(featureStages.toArray(new CompletableFuture[0])) + CompletableFuture.allOf(featureStages.toArray(CompletableFuture[]::new)) .thenApply(aVoid -> ThingsModelFactory.newFeatures( featureStages.stream() diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyPolicyIdStrategy.java b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyPolicyIdStrategy.java index 619214bf168..7bdc0cbba4e 100644 --- a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyPolicyIdStrategy.java +++ b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ModifyPolicyIdStrategy.java @@ -17,6 +17,7 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import org.apache.pekko.actor.ActorSystem; import org.eclipse.ditto.base.model.entity.metadata.Metadata; import org.eclipse.ditto.base.model.headers.DittoHeaders; import org.eclipse.ditto.base.model.headers.WithDittoHeaders; @@ -39,9 +40,11 @@ final class ModifyPolicyIdStrategy extends AbstractThingCommandStrategy> applyModifyCommand(final Context context, final DittoHeaders dittoHeaders = command.getDittoHeaders(); final Thing modifiedThing = applyThingModifications(command.getThing(), thing, eventTs, nextRevision); - - final ThingEvent event = - ThingModified.of(modifiedThing, nextRevision, eventTs, dittoHeaders, metadata); - final WithDittoHeaders response = appendETagHeaderIfProvided(command, - ModifyThingResponse.modified(context.getState(), dittoHeaders), modifiedThing); - - return ResultFactory.newMutationResult(command, event, response); + final CompletionStage validatedStage; + final Optional thingDefinition = thing.getDefinition(); + if (thingDefinition.isPresent()) { + // validate based on potentially referenced Thing WoT TM/TD + validatedStage = wotThingModelValidator + .validateThing(modifiedThing, command.getDittoHeaders()); + } else { + validatedStage = CompletableFuture.completedStage(null); + } + + final CompletionStage> eventStage = + validatedStage.thenApply(aVoid -> + ThingModified.of(modifiedThing, nextRevision, eventTs, dittoHeaders, metadata)); + final CompletionStage responseStage = + validatedStage.thenApply(aVoid -> + appendETagHeaderIfProvided(command, + ModifyThingResponse.modified(context.getState(), dittoHeaders), modifiedThing)); + + return ResultFactory.newMutationResult(command, eventStage, responseStage); } /** diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveAttributeStrategy.java b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveAttributeStrategy.java index 9a689a58a9b..077099779de 100644 --- a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveAttributeStrategy.java +++ b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveAttributeStrategy.java @@ -17,16 +17,17 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; -import org.eclipse.ditto.json.JsonObject; -import org.eclipse.ditto.json.JsonPointer; +import org.apache.pekko.actor.ActorSystem; import org.eclipse.ditto.base.model.entity.metadata.Metadata; import org.eclipse.ditto.base.model.headers.DittoHeaders; import org.eclipse.ditto.base.model.headers.entitytag.EntityTag; +import org.eclipse.ditto.internal.utils.persistentactors.results.Result; +import org.eclipse.ditto.internal.utils.persistentactors.results.ResultFactory; +import org.eclipse.ditto.json.JsonObject; +import org.eclipse.ditto.json.JsonPointer; import org.eclipse.ditto.things.model.Attributes; import org.eclipse.ditto.things.model.Thing; import org.eclipse.ditto.things.model.ThingId; -import org.eclipse.ditto.internal.utils.persistentactors.results.Result; -import org.eclipse.ditto.internal.utils.persistentactors.results.ResultFactory; import org.eclipse.ditto.things.model.signals.commands.query.RetrieveAttribute; import org.eclipse.ditto.things.model.signals.commands.query.RetrieveAttributeResponse; import org.eclipse.ditto.things.model.signals.events.ThingEvent; @@ -39,9 +40,11 @@ final class RetrieveAttributeStrategy extends AbstractThingCommandStrategy { - private final WotThingDescriptionProvider wotThingDescriptionProvider; - /** * Constructs a new {@code RetrieveFeatureStrategy} object. * * @param actorSystem the actor system to use for loading the WoT extension. */ RetrieveFeatureStrategy(final ActorSystem actorSystem) { - super(RetrieveFeature.class); - wotThingDescriptionProvider = WotThingDescriptionProvider.get(actorSystem); + super(RetrieveFeature.class, actorSystem); } @Override @@ -96,7 +92,7 @@ private CompletionStage getRetrieveThingDescriptionResponse(@N if (thing != null) { return thing.getFeatures() .flatMap(f -> f.getFeature(featureId)) - .map(feature -> wotThingDescriptionProvider + .map(feature -> wotThingDescriptionGenerator .provideFeatureTD(command.getEntityId(), thing, feature, command.getDittoHeaders()) ) .map(tdStage -> tdStage.thenApply(td -> diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeaturesStrategy.java b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeaturesStrategy.java index b2b38c3d428..ecc943efe39 100644 --- a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeaturesStrategy.java +++ b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/RetrieveFeaturesStrategy.java @@ -18,6 +18,7 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import org.apache.pekko.actor.ActorSystem; import org.eclipse.ditto.base.model.entity.metadata.Metadata; import org.eclipse.ditto.base.model.headers.DittoHeaders; import org.eclipse.ditto.base.model.headers.entitytag.EntityTag; @@ -43,9 +44,11 @@ final class RetrieveFeaturesStrategy extends AbstractThingCommandStrategy { - private final WotThingDescriptionProvider wotThingDescriptionProvider; - /** * Constructs a new {@code RetrieveThingStrategy} object. * * @param actorSystem the actor system to use for loading the WoT extension. */ RetrieveThingStrategy(final ActorSystem actorSystem) { - super(RetrieveThing.class); - wotThingDescriptionProvider = WotThingDescriptionProvider.get(actorSystem); + super(RetrieveThing.class, actorSystem); } @Override @@ -126,7 +122,7 @@ private static JsonObject getThingJson(final Thing thing, final ThingQueryComman private CompletionStage getRetrieveThingDescriptionResponse(@Nullable final Thing thing, final RetrieveThing command) { if (thing != null) { - return wotThingDescriptionProvider + return wotThingDescriptionGenerator .provideThingTD(thing.getDefinition().orElse(null), command.getEntityId(), thing, diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/SudoRetrieveThingStrategy.java b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/SudoRetrieveThingStrategy.java index 0bbaa07d82a..b29fcb3671b 100644 --- a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/SudoRetrieveThingStrategy.java +++ b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/SudoRetrieveThingStrategy.java @@ -18,6 +18,7 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import org.apache.pekko.actor.ActorSystem; import org.eclipse.ditto.base.model.entity.metadata.Metadata; import org.eclipse.ditto.base.model.headers.entitytag.EntityTag; import org.eclipse.ditto.base.model.json.FieldType; @@ -43,9 +44,11 @@ final class SudoRetrieveThingStrategy extends AbstractThingCommandStrategy> getCre } private void addThingStrategies(final ActorSystem system) { - addStrategy(new ThingConflictStrategy()); - addStrategy(new ModifyThingStrategy()); + addStrategy(new ThingConflictStrategy(system)); + addStrategy(new ModifyThingStrategy(system)); addStrategy(new RetrieveThingStrategy(system)); - addStrategy(new DeleteThingStrategy()); - addStrategy(new MergeThingStrategy()); + addStrategy(new DeleteThingStrategy(system)); + addStrategy(new MergeThingStrategy(system)); } - private void addPolicyStrategies() { - addStrategy(new RetrievePolicyIdStrategy()); - addStrategy(new ModifyPolicyIdStrategy()); + private void addPolicyStrategies(final ActorSystem system) { + addStrategy(new RetrievePolicyIdStrategy(system)); + addStrategy(new ModifyPolicyIdStrategy(system)); } - private void addAttributesStrategies() { - addStrategy(new ModifyAttributesStrategy()); - addStrategy(new ModifyAttributeStrategy()); - addStrategy(new RetrieveAttributesStrategy()); - addStrategy(new RetrieveAttributeStrategy()); - addStrategy(new DeleteAttributesStrategy()); - addStrategy(new DeleteAttributeStrategy()); + private void addAttributesStrategies(final ActorSystem system) { + addStrategy(new ModifyAttributesStrategy(system)); + addStrategy(new ModifyAttributeStrategy(system)); + addStrategy(new RetrieveAttributesStrategy(system)); + addStrategy(new RetrieveAttributeStrategy(system)); + addStrategy(new DeleteAttributesStrategy(system)); + addStrategy(new DeleteAttributeStrategy(system)); } - private void addDefinitionStrategies() { - addStrategy(new ModifyThingDefinitionStrategy()); - addStrategy(new RetrieveThingDefinitionStrategy()); - addStrategy(new DeleteThingDefinitionStrategy()); + private void addDefinitionStrategies(final ActorSystem system) { + addStrategy(new ModifyThingDefinitionStrategy(system)); + addStrategy(new RetrieveThingDefinitionStrategy(system)); + addStrategy(new DeleteThingDefinitionStrategy(system)); } private void addFeaturesStrategies(final ActorSystem system) { addStrategy(new ModifyFeaturesStrategy(system)); addStrategy(new ModifyFeatureStrategy(system)); - addStrategy(new RetrieveFeaturesStrategy()); + addStrategy(new RetrieveFeaturesStrategy(system)); addStrategy(new RetrieveFeatureStrategy(system)); - addStrategy(new DeleteFeaturesStrategy()); - addStrategy(new DeleteFeatureStrategy()); + addStrategy(new DeleteFeaturesStrategy(system)); + addStrategy(new DeleteFeatureStrategy(system)); } - private void addFeatureDefinitionStrategies() { - addStrategy(new ModifyFeatureDefinitionStrategy()); - addStrategy(new RetrieveFeatureDefinitionStrategy()); - addStrategy(new DeleteFeatureDefinitionStrategy()); + private void addFeatureDefinitionStrategies(final ActorSystem system) { + addStrategy(new ModifyFeatureDefinitionStrategy(system)); + addStrategy(new RetrieveFeatureDefinitionStrategy(system)); + addStrategy(new DeleteFeatureDefinitionStrategy(system)); } - private void addFeaturePropertiesStrategies() { - addStrategy(new ModifyFeaturePropertiesStrategy()); - addStrategy(new ModifyFeaturePropertyStrategy()); - addStrategy(new RetrieveFeaturePropertiesStrategy()); - addStrategy(new RetrieveFeaturePropertyStrategy()); - addStrategy(new DeleteFeaturePropertiesStrategy()); - addStrategy(new DeleteFeaturePropertyStrategy()); + private void addFeaturePropertiesStrategies(final ActorSystem system) { + addStrategy(new ModifyFeaturePropertiesStrategy(system)); + addStrategy(new ModifyFeaturePropertyStrategy(system)); + addStrategy(new RetrieveFeaturePropertiesStrategy(system)); + addStrategy(new RetrieveFeaturePropertyStrategy(system)); + addStrategy(new DeleteFeaturePropertiesStrategy(system)); + addStrategy(new DeleteFeaturePropertyStrategy(system)); } - private void addFeatureDesiredPropertiesStrategies() { - addStrategy(new ModifyFeatureDesiredPropertiesStrategy()); - addStrategy(new ModifyFeatureDesiredPropertyStrategy()); - addStrategy(new RetrieveFeatureDesiredPropertiesStrategy()); - addStrategy(new RetrieveFeatureDesiredPropertyStrategy()); - addStrategy(new DeleteFeatureDesiredPropertiesStrategy()); - addStrategy(new DeleteFeatureDesiredPropertyStrategy()); + private void addFeatureDesiredPropertiesStrategies(final ActorSystem system) { + addStrategy(new ModifyFeatureDesiredPropertiesStrategy(system)); + addStrategy(new ModifyFeatureDesiredPropertyStrategy(system)); + addStrategy(new RetrieveFeatureDesiredPropertiesStrategy(system)); + addStrategy(new RetrieveFeatureDesiredPropertyStrategy(system)); + addStrategy(new DeleteFeatureDesiredPropertiesStrategy(system)); + addStrategy(new DeleteFeatureDesiredPropertyStrategy(system)); } - private void addSudoStrategies() { - addStrategy(new SudoRetrieveThingStrategy()); + private void addSudoStrategies(final ActorSystem system) { + addStrategy(new SudoRetrieveThingStrategy(system)); } @Override diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ThingConflictStrategy.java b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ThingConflictStrategy.java index 2b1d2e40e61..1baee618991 100644 --- a/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ThingConflictStrategy.java +++ b/things/service/src/main/java/org/eclipse/ditto/things/service/persistence/actors/strategies/commands/ThingConflictStrategy.java @@ -18,6 +18,7 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import org.apache.pekko.actor.ActorSystem; import org.eclipse.ditto.base.model.entity.metadata.Metadata; import org.eclipse.ditto.base.model.headers.entitytag.EntityTag; import org.eclipse.ditto.internal.utils.persistentactors.results.Result; @@ -36,9 +37,11 @@ final class ThingConflictStrategy extends AbstractThingCommandStrategy context = DefaultContext.getInstance(thingId, @@ -70,7 +74,8 @@ public void createConflictResultWithoutPrecondition() { @Test public void createPreconditionFailedResultWithPrecondition() { - final ThingConflictStrategy underTest = new ThingConflictStrategy(); + final ActorSystem system = ActorSystem.create("test", ConfigFactory.load("test")); + final ThingConflictStrategy underTest = new ThingConflictStrategy(system); final ThingId thingId = ThingId.of("thing:id"); final Thing thing = ThingsModelFactory.newThingBuilder().setId(thingId).setRevision(25L).build(); final CommandStrategy.Context context = DefaultContext.getInstance(thingId, diff --git a/wot/api/pom.xml b/wot/api/pom.xml new file mode 100755 index 00000000000..8a1cce53996 --- /dev/null +++ b/wot/api/pom.xml @@ -0,0 +1,56 @@ + + + + 4.0.0 + + + org.eclipse.ditto + ditto-wot + ${revision} + + + ditto-wot-api + Eclipse Ditto :: WoT :: API + + + + org.eclipse.ditto + ditto-json + + + org.eclipse.ditto + ditto-base-model + + + org.eclipse.ditto + ditto-wot-model + + + org.eclipse.ditto + ditto-wot-validation + + + org.eclipse.ditto + ditto-things-model + + + + org.eclipse.ditto + ditto-internal-utils-cache + + + + diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultFeatureValidationConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultFeatureValidationConfig.java new file mode 100644 index 00000000000..63f131bf192 --- /dev/null +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultFeatureValidationConfig.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.wot.api.config; + +import java.util.Objects; + +import javax.annotation.concurrent.Immutable; + +import org.eclipse.ditto.internal.utils.config.ConfigWithFallback; +import org.eclipse.ditto.internal.utils.config.ScopedConfig; +import org.eclipse.ditto.wot.validation.config.FeatureValidationConfig; + +import com.typesafe.config.Config; + +/** + * This class is the default implementation of the WoT (Web of Things) {@link org.eclipse.ditto.wot.validation.config.FeatureValidationConfig}. + */ +@Immutable +final class DefaultFeatureValidationConfig implements FeatureValidationConfig { + + private static final String CONFIG_PATH = "feature"; + + private final boolean enforceProperties; + private final boolean allowNonModeledProperties; + private final boolean enforceDesiredProperties; + private final boolean allowNonModeledDesiredProperties; + private final boolean enforceInboxMessages; + private final boolean allowNonModeledInboxMessages; + private final boolean enforceOutboxMessages; + private final boolean allowNonModeledOutboxMessages; + + private DefaultFeatureValidationConfig(final ScopedConfig scopedConfig) { + enforceProperties = + scopedConfig.getBoolean(ConfigValue.ENFORCE_PROPERTIES.getConfigPath()); + allowNonModeledProperties = + scopedConfig.getBoolean(ConfigValue.ALLOW_NON_MODELED_PROPERTIES.getConfigPath()); + enforceDesiredProperties = + scopedConfig.getBoolean(ConfigValue.ENFORCE_DESIRED_PROPERTIES.getConfigPath()); + allowNonModeledDesiredProperties = + scopedConfig.getBoolean(ConfigValue.ALLOW_NON_MODELED_DESIRED_PROPERTIES.getConfigPath()); + enforceInboxMessages = + scopedConfig.getBoolean(ConfigValue.ENFORCE_INBOX_MESSAGES.getConfigPath()); + allowNonModeledInboxMessages = + scopedConfig.getBoolean(ConfigValue.ALLOW_NON_MODELED_INBOX_MESSAGES.getConfigPath()); + enforceOutboxMessages = + scopedConfig.getBoolean(ConfigValue.ENFORCE_OUTBOX_MESSAGES.getConfigPath()); + allowNonModeledOutboxMessages = + scopedConfig.getBoolean(ConfigValue.ALLOW_NON_MODELED_OUTBOX_MESSAGES.getConfigPath()); + } + + /** + * Returns an instance of the thing config based on the settings of the specified Config. + * + * @param config is supposed to provide the settings of the thing config at {@value #CONFIG_PATH}. + * @return the instance. + * @throws org.eclipse.ditto.internal.utils.config.DittoConfigError if {@code config} is invalid. + */ + public static DefaultFeatureValidationConfig of(final Config config) { + return new DefaultFeatureValidationConfig(ConfigWithFallback.newInstance(config, CONFIG_PATH, + ConfigValue.values())); + } + + @Override + public boolean isEnforceProperties() { + return enforceProperties; + } + + @Override + public boolean isAllowNonModeledProperties() { + return allowNonModeledProperties; + } + + @Override + public boolean isEnforceDesiredProperties() { + return enforceDesiredProperties; + } + + @Override + public boolean isAllowNonModeledDesiredProperties() { + return allowNonModeledDesiredProperties; + } + + @Override + public boolean isEnforceInboxMessages() { + return enforceInboxMessages; + } + + @Override + public boolean isAllowNonModeledInboxMessages() { + return allowNonModeledInboxMessages; + } + + @Override + public boolean isEnforceOutboxMessages() { + return enforceOutboxMessages; + } + + @Override + public boolean isAllowNonModeledOutboxMessages() { + return allowNonModeledOutboxMessages; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final DefaultFeatureValidationConfig that = (DefaultFeatureValidationConfig) o; + return enforceProperties == that.enforceProperties && + allowNonModeledProperties == that.allowNonModeledProperties && + enforceDesiredProperties == that.enforceDesiredProperties && + allowNonModeledDesiredProperties == that.allowNonModeledDesiredProperties && + enforceInboxMessages == that.enforceInboxMessages && + allowNonModeledInboxMessages == that.allowNonModeledInboxMessages && + enforceOutboxMessages == that.enforceOutboxMessages && + allowNonModeledOutboxMessages == that.allowNonModeledOutboxMessages; + } + + @Override + public int hashCode() { + return Objects.hash(enforceProperties, allowNonModeledProperties, enforceDesiredProperties, + allowNonModeledDesiredProperties, enforceInboxMessages, allowNonModeledInboxMessages, + enforceOutboxMessages, allowNonModeledOutboxMessages); + } + + @Override + public String toString() { + return getClass().getSimpleName() + " [" + + "enforceProperties=" + enforceProperties + + ", allowNonModeledProperties=" + allowNonModeledProperties + + ", enforceDesiredProperties=" + enforceDesiredProperties + + ", allowNonModeledDesiredProperties=" + allowNonModeledDesiredProperties + + ", enforceInboxMessages=" + enforceInboxMessages + + ", allowNonModeledInboxMessages=" + allowNonModeledInboxMessages + + ", enforceOutboxMessages=" + enforceOutboxMessages + + ", allowNonModeledOutboxMessages=" + allowNonModeledOutboxMessages + + "]"; + } +} diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultThingValidationConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultThingValidationConfig.java new file mode 100644 index 00000000000..cf04dc43584 --- /dev/null +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultThingValidationConfig.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.wot.api.config; + +import java.util.Objects; + +import javax.annotation.concurrent.Immutable; + +import org.eclipse.ditto.internal.utils.config.ConfigWithFallback; +import org.eclipse.ditto.internal.utils.config.ScopedConfig; +import org.eclipse.ditto.wot.validation.config.ThingValidationConfig; + +import com.typesafe.config.Config; + +/** + * This class is the default implementation of the WoT (Web of Things) {@link org.eclipse.ditto.wot.validation.config.ThingValidationConfig}. + */ +@Immutable +final class DefaultThingValidationConfig implements ThingValidationConfig { + + private static final String CONFIG_PATH = "thing"; + + private final boolean enforceAttributes; + private final boolean allowNonModeledAttributes; + private final boolean enforceInboxMessages; + private final boolean allowNonModeledInboxMessages; + private final boolean enforceOutboxMessages; + private final boolean allowNonModeledOutboxMessages; + + private DefaultThingValidationConfig(final ScopedConfig scopedConfig) { + enforceAttributes = + scopedConfig.getBoolean(ConfigValue.ENFORCE_ATTRIBUTES.getConfigPath()); + allowNonModeledAttributes = + scopedConfig.getBoolean(ConfigValue.ALLOW_NON_MODELED_ATTRIBUTES.getConfigPath()); + enforceInboxMessages = + scopedConfig.getBoolean(ConfigValue.ENFORCE_INBOX_MESSAGES.getConfigPath()); + allowNonModeledInboxMessages = + scopedConfig.getBoolean(ConfigValue.ALLOW_NON_MODELED_INBOX_MESSAGES.getConfigPath()); + enforceOutboxMessages = + scopedConfig.getBoolean(ConfigValue.ENFORCE_OUTBOX_MESSAGES.getConfigPath()); + allowNonModeledOutboxMessages = + scopedConfig.getBoolean(ConfigValue.ALLOW_NON_MODELED_OUTBOX_MESSAGES.getConfigPath()); + } + + /** + * Returns an instance of the thing config based on the settings of the specified Config. + * + * @param config is supposed to provide the settings of the thing config at {@value #CONFIG_PATH}. + * @return the instance. + * @throws org.eclipse.ditto.internal.utils.config.DittoConfigError if {@code config} is invalid. + */ + public static DefaultThingValidationConfig of(final Config config) { + return new DefaultThingValidationConfig(ConfigWithFallback.newInstance(config, CONFIG_PATH, + ConfigValue.values())); + } + + @Override + public boolean isEnforceAttributes() { + return enforceAttributes; + } + + @Override + public boolean isAllowNonModeledAttributes() { + return allowNonModeledAttributes; + } + + @Override + public boolean isEnforceInboxMessages() { + return enforceInboxMessages; + } + + @Override + public boolean isAllowNonModeledInboxMessages() { + return allowNonModeledInboxMessages; + } + + @Override + public boolean isEnforceOutboxMessages() { + return enforceOutboxMessages; + } + + @Override + public boolean isAllowNonModeledOutboxMessages() { + return allowNonModeledOutboxMessages; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final DefaultThingValidationConfig that = (DefaultThingValidationConfig) o; + return enforceAttributes == that.enforceAttributes && + allowNonModeledAttributes == that.allowNonModeledAttributes && + enforceInboxMessages == that.enforceInboxMessages && + allowNonModeledInboxMessages == that.allowNonModeledInboxMessages && + enforceOutboxMessages == that.enforceOutboxMessages && + allowNonModeledOutboxMessages == that.allowNonModeledOutboxMessages; + } + + @Override + public int hashCode() { + return Objects.hash(enforceAttributes, allowNonModeledAttributes, enforceInboxMessages, + allowNonModeledInboxMessages, enforceOutboxMessages, allowNonModeledOutboxMessages); + } + + @Override + public String toString() { + return getClass().getSimpleName() + " [" + + "enforceAttributes=" + enforceAttributes + + ", allowNonModeledAttributes=" + allowNonModeledAttributes + + ", enforceInboxMessages=" + enforceInboxMessages + + ", allowNonModeledInboxMessages=" + allowNonModeledInboxMessages + + ", enforceOutboxMessages=" + enforceOutboxMessages + + ", allowNonModeledOutboxMessages=" + allowNonModeledOutboxMessages + + "]"; + } +} diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultTmBasedCreationConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultTmBasedCreationConfig.java similarity index 96% rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultTmBasedCreationConfig.java rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultTmBasedCreationConfig.java index 1039a202eff..25cefa6b87b 100644 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultTmBasedCreationConfig.java +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultTmBasedCreationConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.wot.integration.config; +package org.eclipse.ditto.wot.api.config; import java.util.Objects; diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultTmScopedCreationConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultTmScopedCreationConfig.java similarity index 96% rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultTmScopedCreationConfig.java rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultTmScopedCreationConfig.java index f56139c3a09..4f0640ea0ec 100644 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultTmScopedCreationConfig.java +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultTmScopedCreationConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.wot.integration.config; +package org.eclipse.ditto.wot.api.config; import java.util.Objects; diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultTmValidationConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultTmValidationConfig.java new file mode 100644 index 00000000000..1c082826266 --- /dev/null +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultTmValidationConfig.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.wot.api.config; + +import java.util.Objects; + +import javax.annotation.concurrent.Immutable; + +import org.eclipse.ditto.internal.utils.config.ConfigWithFallback; +import org.eclipse.ditto.internal.utils.config.ScopedConfig; +import org.eclipse.ditto.wot.validation.config.FeatureValidationConfig; +import org.eclipse.ditto.wot.validation.config.ThingValidationConfig; +import org.eclipse.ditto.wot.validation.config.TmValidationConfig; + +import com.typesafe.config.Config; + +/** + * This class is the default implementation of the WoT (Web of Things) {@link org.eclipse.ditto.wot.validation.config.TmValidationConfig}. + */ +@Immutable +final class DefaultTmValidationConfig implements TmValidationConfig { + + private static final String CONFIG_PATH = "tm-model-validation"; + + private final boolean enabled; + private final ThingValidationConfig thingValidationConfig; + private final FeatureValidationConfig featureValidationConfig; + + private DefaultTmValidationConfig(final ScopedConfig scopedConfig) { + enabled = scopedConfig.getBoolean(ConfigValue.ENABLED.getConfigPath()); + thingValidationConfig = DefaultThingValidationConfig.of(scopedConfig); + featureValidationConfig = DefaultFeatureValidationConfig.of(scopedConfig); + } + + /** + * Returns an instance of the thing config based on the settings of the specified Config. + * + * @param config is supposed to provide the settings of the thing config at {@value #CONFIG_PATH}. + * @return the instance. + * @throws org.eclipse.ditto.internal.utils.config.DittoConfigError if {@code config} is invalid. + */ + public static DefaultTmValidationConfig of(final Config config) { + return new DefaultTmValidationConfig(ConfigWithFallback.newInstance(config, CONFIG_PATH, + ConfigValue.values())); + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public ThingValidationConfig getThingValidationConfig() { + return thingValidationConfig; + } + + @Override + public FeatureValidationConfig getFeatureValidationConfig() { + return featureValidationConfig; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final DefaultTmValidationConfig that = (DefaultTmValidationConfig) o; + return enabled == that.enabled && + Objects.equals(thingValidationConfig, that.thingValidationConfig) && + Objects.equals(featureValidationConfig, that.featureValidationConfig); + } + + @Override + public int hashCode() { + return Objects.hash(enabled, thingValidationConfig, featureValidationConfig); + } + + @Override + public String toString() { + return getClass().getSimpleName() + " [" + + "enabled=" + enabled + + ", thingValidationConfig=" + thingValidationConfig + + ", featureValidationConfig=" + featureValidationConfig + + "]"; + } +} diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultToThingDescriptionConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultToThingDescriptionConfig.java similarity index 97% rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultToThingDescriptionConfig.java rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultToThingDescriptionConfig.java index 172775dbd36..92582a4d4c0 100644 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultToThingDescriptionConfig.java +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultToThingDescriptionConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.wot.integration.config; +package org.eclipse.ditto.wot.api.config; import java.util.Map; import java.util.Objects; diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultWotConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultWotConfig.java similarity index 74% rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultWotConfig.java rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultWotConfig.java index 052dc3a7a5c..63c8ac07644 100644 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/DefaultWotConfig.java +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/DefaultWotConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,18 +10,19 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.wot.integration.config; +package org.eclipse.ditto.wot.api.config; import java.util.Objects; import javax.annotation.concurrent.Immutable; -import org.eclipse.ditto.base.service.config.http.DefaultHttpProxyConfig; -import org.eclipse.ditto.base.service.config.http.HttpProxyConfig; import org.eclipse.ditto.internal.utils.cache.config.CacheConfig; import org.eclipse.ditto.internal.utils.cache.config.DefaultCacheConfig; import org.eclipse.ditto.internal.utils.config.DefaultScopedConfig; import org.eclipse.ditto.internal.utils.config.ScopedConfig; +import org.eclipse.ditto.internal.utils.config.http.DefaultHttpProxyBaseConfig; +import org.eclipse.ditto.internal.utils.config.http.HttpProxyBaseConfig; +import org.eclipse.ditto.wot.validation.config.TmValidationConfig; import com.typesafe.config.Config; @@ -33,18 +34,25 @@ @Immutable public final class DefaultWotConfig implements WotConfig { + /** + * The parent path of the "wot" config. + */ + public static final String WOT_PARENT_CONFIG_PATH = "things"; + private static final String CONFIG_PATH = "wot"; - private final HttpProxyConfig httpProxyConfig; + private final HttpProxyBaseConfig httpProxyConfig; private final CacheConfig cacheConfig; private final ToThingDescriptionConfig toThingDescriptionConfig; - private final DefaultTmBasedCreationConfig tmBasedCreationConfig; + private final TmBasedCreationConfig tmBasedCreationConfig; + private final TmValidationConfig tmValidationConfig; private DefaultWotConfig(final ScopedConfig scopedConfig) { - httpProxyConfig = DefaultHttpProxyConfig.ofHttpProxy(scopedConfig); + httpProxyConfig = DefaultHttpProxyBaseConfig.ofHttpProxy(scopedConfig); cacheConfig = DefaultCacheConfig.of(scopedConfig, "cache"); toThingDescriptionConfig = DefaultToThingDescriptionConfig.of(scopedConfig); tmBasedCreationConfig = DefaultTmBasedCreationConfig.of(scopedConfig); + tmValidationConfig = DefaultTmValidationConfig.of(scopedConfig); } /** @@ -59,7 +67,7 @@ public static DefaultWotConfig of(final Config config) { } @Override - public HttpProxyConfig getHttpProxyConfig() { + public HttpProxyBaseConfig getHttpProxyConfig() { return httpProxyConfig; } @@ -78,6 +86,11 @@ public TmBasedCreationConfig getCreationConfig() { return tmBasedCreationConfig; } + @Override + public TmValidationConfig getValidationConfig() { + return tmValidationConfig; + } + @Override public boolean equals(final Object o) { if (this == o) { @@ -90,12 +103,13 @@ public boolean equals(final Object o) { return Objects.equals(httpProxyConfig, that.httpProxyConfig) && Objects.equals(cacheConfig, that.cacheConfig) && Objects.equals(toThingDescriptionConfig, that.toThingDescriptionConfig) && - Objects.equals(tmBasedCreationConfig, that.tmBasedCreationConfig); + Objects.equals(tmBasedCreationConfig, that.tmBasedCreationConfig) && + Objects.equals(tmValidationConfig, that.tmValidationConfig); } @Override public int hashCode() { - return Objects.hash(httpProxyConfig, cacheConfig, toThingDescriptionConfig, tmBasedCreationConfig); + return Objects.hash(httpProxyConfig, cacheConfig, toThingDescriptionConfig, tmBasedCreationConfig, tmValidationConfig); } @Override @@ -105,6 +119,7 @@ public String toString() { ", cacheConfig=" + cacheConfig + ", toThingDescriptionConfig=" + toThingDescriptionConfig + ", tmBasedCreationConfig=" + tmBasedCreationConfig + + ", tmValidationConfig=" + tmValidationConfig + "]"; } diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/TmBasedCreationConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/TmBasedCreationConfig.java similarity index 94% rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/TmBasedCreationConfig.java rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/config/TmBasedCreationConfig.java index 564bb98c784..a4754ebb4f6 100644 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/TmBasedCreationConfig.java +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/TmBasedCreationConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.wot.integration.config; +package org.eclipse.ditto.wot.api.config; import javax.annotation.concurrent.Immutable; diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/TmScopedCreationConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/TmScopedCreationConfig.java similarity index 96% rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/TmScopedCreationConfig.java rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/config/TmScopedCreationConfig.java index 9735b76808d..bdd1cb6f719 100644 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/TmScopedCreationConfig.java +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/TmScopedCreationConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.wot.integration.config; +package org.eclipse.ditto.wot.api.config; import javax.annotation.concurrent.Immutable; diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/ToThingDescriptionConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/ToThingDescriptionConfig.java similarity index 96% rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/ToThingDescriptionConfig.java rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/config/ToThingDescriptionConfig.java index 9f591aebb68..d68183cc88b 100644 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/ToThingDescriptionConfig.java +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/ToThingDescriptionConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.wot.integration.config; +package org.eclipse.ditto.wot.api.config; import java.util.Map; diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/WotConfig.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/WotConfig.java similarity index 70% rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/WotConfig.java rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/config/WotConfig.java index c36b357efd9..3b64d290c83 100644 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/WotConfig.java +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/WotConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,12 +10,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.wot.integration.config; +package org.eclipse.ditto.wot.api.config; import javax.annotation.concurrent.Immutable; -import org.eclipse.ditto.base.service.config.http.HttpProxyConfig; import org.eclipse.ditto.internal.utils.cache.config.CacheConfig; +import org.eclipse.ditto.internal.utils.config.http.HttpProxyBaseConfig; +import org.eclipse.ditto.wot.validation.config.TmValidationConfig; /** * Provides configuration settings for WoT (Web of Things) integration. @@ -30,7 +31,7 @@ public interface WotConfig { * * @return configuration settings for the HTTP proxy. */ - HttpProxyConfig getHttpProxyConfig(); + HttpProxyBaseConfig getHttpProxyConfig(); /** * Returns the cache configuration to apply for caching downloaded WoT Thing Models. @@ -50,8 +51,15 @@ public interface WotConfig { /** * Returns configuration for WoT TM (ThingModel) based creation of Things and Features. * - * @return configuration for WoT TM (ThingModel) based creation of Things and Features. + * @return configuration for WoT TM (ThingModel) based creation of Things and Features. */ TmBasedCreationConfig getCreationConfig(); + /** + * @return configuration for WoT (Web of Things) integration regarding the validation of Things and Features + * based on their WoT ThingModels. + * @since 3.6.0 + */ + TmValidationConfig getValidationConfig(); + } diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/package-info.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/package-info.java similarity index 80% rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/package-info.java rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/config/package-info.java index c8a84b71858..92fb86e9274 100644 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/config/package-info.java +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/config/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -13,7 +13,7 @@ /** * Configuration for the WoT (Web of Things) integration. - * @since 2.4.0 + * @since 3.6.0 */ @org.eclipse.ditto.utils.jsr305.annotations.AllParametersAndReturnValuesAreNonnullByDefault -package org.eclipse.ditto.wot.integration.config; \ No newline at end of file +package org.eclipse.ditto.wot.api.config; \ No newline at end of file diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DefaultWotThingDescriptionGenerator.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DefaultWotThingDescriptionGenerator.java similarity index 86% rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DefaultWotThingDescriptionGenerator.java rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DefaultWotThingDescriptionGenerator.java index a687ba9096c..5a778fd0c6c 100644 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DefaultWotThingDescriptionGenerator.java +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DefaultWotThingDescriptionGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.wot.integration.generator; +package org.eclipse.ditto.wot.api.generator; import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull; @@ -23,7 +23,9 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.regex.Matcher; @@ -39,8 +41,6 @@ import org.eclipse.ditto.base.model.headers.DittoHeaderDefinition; import org.eclipse.ditto.base.model.headers.DittoHeaders; import org.eclipse.ditto.base.model.headers.contenttype.ContentType; -import org.eclipse.ditto.internal.utils.pekko.logging.DittoLogger; -import org.eclipse.ditto.internal.utils.pekko.logging.DittoLoggerFactory; import org.eclipse.ditto.json.JsonArray; import org.eclipse.ditto.json.JsonCollectors; import org.eclipse.ditto.json.JsonField; @@ -48,12 +48,15 @@ import org.eclipse.ditto.json.JsonObject; import org.eclipse.ditto.json.JsonPointer; import org.eclipse.ditto.json.JsonValue; +import org.eclipse.ditto.things.model.DefinitionIdentifier; import org.eclipse.ditto.things.model.Feature; +import org.eclipse.ditto.things.model.FeatureDefinition; import org.eclipse.ditto.things.model.Thing; +import org.eclipse.ditto.things.model.ThingDefinition; import org.eclipse.ditto.things.model.ThingId; -import org.eclipse.ditto.wot.integration.config.ToThingDescriptionConfig; -import org.eclipse.ditto.wot.integration.config.WotConfig; -import org.eclipse.ditto.wot.integration.provider.WotThingModelFetcher; +import org.eclipse.ditto.wot.api.config.ToThingDescriptionConfig; +import org.eclipse.ditto.wot.api.config.WotConfig; +import org.eclipse.ditto.wot.api.resolver.WotThingModelResolver; import org.eclipse.ditto.wot.model.Action; import org.eclipse.ditto.wot.model.ActionFormElement; import org.eclipse.ditto.wot.model.ActionForms; @@ -90,15 +93,17 @@ import org.eclipse.ditto.wot.model.SinglePropertyFormElementOp; import org.eclipse.ditto.wot.model.SingleRootFormElementOp; import org.eclipse.ditto.wot.model.StringSchema; +import org.eclipse.ditto.wot.model.ThingDefinitionInvalidException; import org.eclipse.ditto.wot.model.ThingDescription; import org.eclipse.ditto.wot.model.ThingModel; import org.eclipse.ditto.wot.model.Title; import org.eclipse.ditto.wot.model.UriVariables; import org.eclipse.ditto.wot.model.Version; +import org.eclipse.ditto.wot.model.WotInternalErrorException; import org.eclipse.ditto.wot.model.WotThingModelInvalidException; import org.eclipse.ditto.wot.model.WotThingModelPlaceholderUnresolvedException; - -import org.apache.pekko.actor.ActorSystem; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Default Ditto specific implementation of {@link WotThingDescriptionGenerator}. @@ -106,8 +111,7 @@ @Immutable final class DefaultWotThingDescriptionGenerator implements WotThingDescriptionGenerator { - private static final DittoLogger LOGGER = - DittoLoggerFactory.getLogger(DefaultWotThingDescriptionGenerator.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultWotThingDescriptionGenerator.class); private static final String TM_PLACEHOLDER_PL_GROUP = "pl"; private static final Pattern TM_PLACEHOLDER_PATTERN = @@ -124,15 +128,134 @@ final class DefaultWotThingDescriptionGenerator implements WotThingDescriptionGe private static final String SCHEMA_DITTO_ERROR = "dittoError"; private static final String DITTO_FIELDS_URI_VARIABLE = "fields"; + private static final String MODEL_PLACEHOLDERS_KEY = "model-placeholders"; + private final ToThingDescriptionConfig toThingDescriptionConfig; - private final WotThingModelExtensionResolver thingModelExtensionResolver; + private final WotThingModelResolver thingModelResolver; + private final Executor executor; - DefaultWotThingDescriptionGenerator(final ActorSystem actorSystem, + DefaultWotThingDescriptionGenerator( final WotConfig wotConfig, - final WotThingModelFetcher thingModelFetcher) { + final WotThingModelResolver thingModelResolver, + final Executor executor) { this.toThingDescriptionConfig = checkNotNull(wotConfig, "wotConfig").getToThingDescriptionConfig(); - thingModelExtensionResolver = new DefaultWotThingModelExtensionResolver(thingModelFetcher, - actorSystem.dispatchers().lookup("wot-dispatcher")); + this.thingModelResolver = checkNotNull(thingModelResolver, "thingModelResolver"); + this.executor = executor; + } + + @Override + public CompletionStage provideThingTD(@Nullable final ThingDefinition thingDefinition, + final ThingId thingId, + @Nullable final Thing thing, + final DittoHeaders dittoHeaders) { + if (null != thingDefinition) { + return getWotThingDescriptionForThing(thingDefinition, thingId, thing, dittoHeaders); + } else { + throw ThingDefinitionInvalidException.newBuilder(null) + .dittoHeaders(dittoHeaders) + .build(); + } + } + + @Override + public CompletionStage provideFeatureTD(final ThingId thingId, + @Nullable final Thing thing, + final Feature feature, + final DittoHeaders dittoHeaders) { + + checkNotNull(feature, "feature"); + if (feature.getDefinition().isPresent()) { + return getWotThingDescriptionForFeature(thingId, thing, feature, dittoHeaders); + } else { + throw ThingDefinitionInvalidException.newBuilder(null) + .dittoHeaders(dittoHeaders) + .build(); + } + } + + + + /** + * Download TM, add it to local cache + build TD + return it + */ + private CompletionStage getWotThingDescriptionForThing(final ThingDefinition definitionIdentifier, + final ThingId thingId, + @Nullable final Thing thing, + final DittoHeaders dittoHeaders) { + + final Optional urlOpt = definitionIdentifier.getUrl(); + if (urlOpt.isPresent()) { + final URL url = urlOpt.get(); + return thingModelResolver.resolveThingModel(url, dittoHeaders) + .thenComposeAsync(thingModel -> generateThingDescription(thingId, + thing, + Optional.ofNullable(thing) + .flatMap(Thing::getAttributes) + .flatMap(a -> a.getValue(MODEL_PLACEHOLDERS_KEY)) + .filter(JsonValue::isObject) + .map(JsonValue::asObject) + .orElse(null), + null, + thingModel, + url, + dittoHeaders + ), + executor + ) + .exceptionally(throwable -> { + throw DittoRuntimeException.asDittoRuntimeException(throwable, t -> + WotInternalErrorException.newBuilder() + .dittoHeaders(dittoHeaders) + .cause(t) + .build()); + }); + } else { + throw ThingDefinitionInvalidException.newBuilder(definitionIdentifier) + .dittoHeaders(dittoHeaders) + .build(); + } + } + + /** + * Download TM, add it to local cache + build TD + return it + */ + private CompletionStage getWotThingDescriptionForFeature(final ThingId thingId, + @Nullable final Thing thing, + final Feature feature, + final DittoHeaders dittoHeaders) { + + final Optional definitionIdentifier = feature.getDefinition() + .map(FeatureDefinition::getFirstIdentifier); + final Optional urlOpt = definitionIdentifier.flatMap(DefinitionIdentifier::getUrl); + if (urlOpt.isPresent()) { + final URL url = urlOpt.get(); + return thingModelResolver.resolveThingModel(url, dittoHeaders) + .thenComposeAsync(thingModel -> generateThingDescription(thingId, + thing, + feature.getProperties() + .flatMap(p -> p.getValue(MODEL_PLACEHOLDERS_KEY)) + .filter(JsonValue::isObject) + .map(JsonValue::asObject) + .orElse(null), + feature.getId(), + thingModel, + url, + dittoHeaders + ), + executor + ) + .exceptionally(throwable -> { + throw DittoRuntimeException.asDittoRuntimeException(throwable, t -> + WotInternalErrorException.newBuilder() + .dittoHeaders(dittoHeaders) + .cause(t) + .build()); + }); + } else { + throw ThingDefinitionInvalidException.newBuilder(definitionIdentifier.orElse(null)) + .dittoHeaders(dittoHeaders) + .build(); + } } @Override @@ -145,15 +268,10 @@ public CompletionStage generateThingDescription(final ThingId final DittoHeaders dittoHeaders) { // generation rules defined at: https://w3c.github.io/wot-thing-description/#thing-model-td-generation - return thingModelExtensionResolver - .resolveThingModelExtensions(thingModel, dittoHeaders) - .thenCompose(thingModelWithExtensions -> - thingModelExtensionResolver.resolveThingModelRefs(thingModelWithExtensions, dittoHeaders) - ) + return CompletableFuture.completedFuture(thingModel) .thenApply(thingModelWithExtensionsAndImports -> { - LOGGER.withCorrelationId(dittoHeaders) - .debug("ThingModel after resolving extensions + refs: <{}>", - thingModelWithExtensionsAndImports); + LOGGER.debug("ThingModel after resolving extensions + refs: <{}>", + thingModelWithExtensionsAndImports); final ThingModel cleanedTm = removeThingModelSpecificElements(thingModelWithExtensionsAndImports, dittoHeaders); @@ -184,10 +302,8 @@ public CompletionStage generateThingDescription(final ThingId final ThingDescription thingDescription = resolvePlaceholders(tdBuilder.build(), placeholderLookupObject, dittoHeaders); - LOGGER.withCorrelationId(dittoHeaders) - .info("Created ThingDescription for thingId <{}> and featureId <{}>: <{}>", thingId, - featureId, - thingDescription); + LOGGER.info("Created ThingDescription for thingId <{}> and featureId <{}>: <{}>", thingId, + featureId, thingDescription); return thingDescription; }); } diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DefaultWotThingModelExtensionResolver.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DefaultWotThingModelExtensionResolver.java similarity index 97% rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DefaultWotThingModelExtensionResolver.java rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DefaultWotThingModelExtensionResolver.java index 631d2673d86..231c453e3c9 100644 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DefaultWotThingModelExtensionResolver.java +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DefaultWotThingModelExtensionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.wot.integration.generator; +package org.eclipse.ditto.wot.api.generator; import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull; @@ -27,7 +27,7 @@ import org.eclipse.ditto.json.JsonObject; import org.eclipse.ditto.json.JsonPointer; import org.eclipse.ditto.json.JsonValue; -import org.eclipse.ditto.wot.integration.provider.WotThingModelFetcher; +import org.eclipse.ditto.wot.api.provider.WotThingModelFetcher; import org.eclipse.ditto.wot.model.IRI; import org.eclipse.ditto.wot.model.ThingModel; import org.eclipse.ditto.wot.model.WotThingModelRefInvalidException; @@ -43,8 +43,7 @@ final class DefaultWotThingModelExtensionResolver implements WotThingModelExtens private final WotThingModelFetcher thingModelFetcher; private final Executor executor; - DefaultWotThingModelExtensionResolver(final WotThingModelFetcher thingModelFetcher, - final Executor executor) { + DefaultWotThingModelExtensionResolver(final WotThingModelFetcher thingModelFetcher, final Executor executor) { this.thingModelFetcher = checkNotNull(thingModelFetcher, "thingModelFetcher"); this.executor = executor; } diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DefaultWotThingSkeletonGenerator.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DefaultWotThingSkeletonGenerator.java similarity index 74% rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DefaultWotThingSkeletonGenerator.java rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DefaultWotThingSkeletonGenerator.java index 32534431ff1..5b2b5d886f4 100644 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DefaultWotThingSkeletonGenerator.java +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DefaultWotThingSkeletonGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,12 +10,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.wot.integration.generator; +package org.eclipse.ditto.wot.api.generator; import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull; import java.net.MalformedURLException; import java.net.URL; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; @@ -32,11 +33,9 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; -import org.apache.pekko.actor.ActorSystem; -import org.apache.pekko.japi.Pair; +import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException; import org.eclipse.ditto.base.model.headers.DittoHeaders; -import org.eclipse.ditto.internal.utils.pekko.logging.DittoLoggerFactory; -import org.eclipse.ditto.internal.utils.pekko.logging.ThreadSafeDittoLogger; +import org.eclipse.ditto.base.model.signals.FeatureToggle; import org.eclipse.ditto.json.JsonArray; import org.eclipse.ditto.json.JsonArrayBuilder; import org.eclipse.ditto.json.JsonObject; @@ -54,9 +53,11 @@ import org.eclipse.ditto.things.model.FeaturesBuilder; import org.eclipse.ditto.things.model.Thing; import org.eclipse.ditto.things.model.ThingBuilder; +import org.eclipse.ditto.things.model.ThingDefinition; import org.eclipse.ditto.things.model.ThingId; import org.eclipse.ditto.things.model.ThingsModelFactory; -import org.eclipse.ditto.wot.integration.provider.WotThingModelFetcher; +import org.eclipse.ditto.wot.api.config.WotConfig; +import org.eclipse.ditto.wot.api.resolver.WotThingModelResolver; import org.eclipse.ditto.wot.model.ArraySchema; import org.eclipse.ditto.wot.model.BaseLink; import org.eclipse.ditto.wot.model.DataSchemaType; @@ -71,7 +72,10 @@ import org.eclipse.ditto.wot.model.ThingDefinitionInvalidException; import org.eclipse.ditto.wot.model.ThingModel; import org.eclipse.ditto.wot.model.TmOptional; +import org.eclipse.ditto.wot.model.WotInternalErrorException; import org.eclipse.ditto.wot.model.WotThingModelInvalidException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Default Ditto specific implementation of {@link WotThingSkeletonGenerator}. @@ -79,22 +83,134 @@ @Immutable final class DefaultWotThingSkeletonGenerator implements WotThingSkeletonGenerator { - private static final ThreadSafeDittoLogger LOGGER = - DittoLoggerFactory.getThreadSafeLogger(DefaultWotThingSkeletonGenerator.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultWotThingSkeletonGenerator.class); private static final String TM_EXTENDS = "tm:extends"; private static final String TM_SUBMODEL = "tm:submodel"; private static final String TM_SUBMODEL_INSTANCE_NAME = "instanceName"; - private final WotThingModelFetcher thingModelFetcher; + private final WotConfig wotConfig; + private final WotThingModelResolver thingModelResolver; private final Executor executor; - private final WotThingModelExtensionResolver thingModelExtensionResolver; - DefaultWotThingSkeletonGenerator(final ActorSystem actorSystem, final WotThingModelFetcher thingModelFetcher) { - this.thingModelFetcher = checkNotNull(thingModelFetcher, "thingModelFetcher"); - executor = actorSystem.dispatchers().lookup("wot-dispatcher"); - thingModelExtensionResolver = new DefaultWotThingModelExtensionResolver(thingModelFetcher, executor); + DefaultWotThingSkeletonGenerator(final WotConfig wotConfig, + final WotThingModelResolver thingModelResolver, + final Executor executor) { + this.thingModelResolver = checkNotNull(thingModelResolver, "thingModelResolver"); + this.wotConfig = checkNotNull(wotConfig, "wotConfig"); + this.executor = executor; + } + + @Override + public CompletionStage> provideThingSkeletonForCreation(final ThingId thingId, + @Nullable final ThingDefinition thingDefinition, + final DittoHeaders dittoHeaders) { + + if (FeatureToggle.isWotIntegrationFeatureEnabled() && + wotConfig.getCreationConfig().getThingCreationConfig().isSkeletonCreationEnabled() && + null != thingDefinition) { + final Optional urlOpt = thingDefinition.getUrl(); + if (urlOpt.isPresent()) { + final URL url = urlOpt.get(); + LOGGER.debug("Resolving ThingModel from <{}> in order to create Thing skeleton for new Thing " + + "with id <{}>", url, thingId); + return thingModelResolver.resolveThingModel(url, dittoHeaders) + .thenComposeAsync(thingModel -> generateThingSkeleton( + thingId, + thingModel, + url, + wotConfig.getCreationConfig() + .getThingCreationConfig() + .shouldGenerateDefaultsForOptionalProperties(), + dittoHeaders + ), + executor + ) + .handle((thingSkeleton, throwable) -> { + if (throwable != null) { + LOGGER.info("Could not fetch ThingModel or generate Thing skeleton based on it due " + + "to: <{}: {}>", + throwable.getClass().getSimpleName(), throwable.getMessage(), throwable); + if (wotConfig.getCreationConfig() + .getThingCreationConfig() + .shouldThrowExceptionOnWotErrors()) { + throw DittoRuntimeException.asDittoRuntimeException( + throwable, t -> WotInternalErrorException.newBuilder() + .dittoHeaders(dittoHeaders) + .cause(t) + .build() + ); + } else { + return Optional.empty(); + } + } else { + LOGGER.debug("Created Thing skeleton for new Thing with id <{}>: <{}>", thingId, + thingSkeleton); + return thingSkeleton; + } + }); + } else { + return CompletableFuture.completedFuture(Optional.empty()); + } + } else { + return CompletableFuture.completedFuture(Optional.empty()); + } + } + + @Override + public CompletionStage> provideFeatureSkeletonForCreation(final String featureId, + @Nullable final FeatureDefinition featureDefinition, final DittoHeaders dittoHeaders) { + + if (FeatureToggle.isWotIntegrationFeatureEnabled() && + wotConfig.getCreationConfig().getFeatureCreationConfig().isSkeletonCreationEnabled() && + null != featureDefinition) { + final Optional urlOpt = featureDefinition.getFirstIdentifier().getUrl(); + if (urlOpt.isPresent()) { + final URL url = urlOpt.get(); + LOGGER.debug("Resolving ThingModel from <{}> in order to create Feature skeleton for new Feature " + + "with id <{}>", url, featureId); + return thingModelResolver.resolveThingModel(url, dittoHeaders) + .thenComposeAsync(thingModel -> generateFeatureSkeleton( + featureId, + thingModel, + url, + wotConfig.getCreationConfig() + .getFeatureCreationConfig() + .shouldGenerateDefaultsForOptionalProperties(), + dittoHeaders + ), + executor + ) + .handle((featureSkeleton, throwable) -> { + if (throwable != null) { + LOGGER.info("Could not fetch ThingModel or generate Feature skeleton based on it due " + + "to: <{}: {}>", + throwable.getClass().getSimpleName(), throwable.getMessage(), throwable); + if (wotConfig.getCreationConfig() + .getFeatureCreationConfig() + .shouldThrowExceptionOnWotErrors()) { + throw DittoRuntimeException.asDittoRuntimeException( + throwable, t -> WotInternalErrorException.newBuilder() + .dittoHeaders(dittoHeaders) + .cause(t) + .build() + ); + } else { + return Optional.empty(); + } + } else { + LOGGER.debug("Created Feature skeleton for new Feature with id <{}>: <{}>", featureId, + featureSkeleton); + return featureSkeleton; + } + }); + } else { + return CompletableFuture.completedFuture(Optional.empty()); + } + } else { + return CompletableFuture.completedFuture(Optional.empty()); + } } @Override @@ -104,18 +220,13 @@ public CompletionStage> generateThingSkeleton(final ThingId thin final boolean generateDefaultsForOptionalProperties, final DittoHeaders dittoHeaders) { - return thingModelExtensionResolver - .resolveThingModelExtensions(thingModel, dittoHeaders) - .thenCompose(thingModelWithExtensions -> - thingModelExtensionResolver.resolveThingModelRefs(thingModelWithExtensions, dittoHeaders) - ) + return CompletableFuture.completedFuture(thingModel) .thenApply(thingModelWithExtensionsAndImports -> { final Optional dittoExtensionPrefix = thingModelWithExtensionsAndImports.getAtContext() .determinePrefixFor(DittoWotExtension.DITTO_WOT_EXTENSION); - LOGGER.withCorrelationId(dittoHeaders) - .debug("ThingModel for generating Thing skeleton after resolving extensions + refs: <{}>", - thingModelWithExtensionsAndImports); + LOGGER.debug("ThingModel for generating Thing skeleton after resolving extensions + refs: <{}>", + thingModelWithExtensionsAndImports); final ThingBuilder.FromScratch builder = Thing.newBuilder(); thingModelWithExtensionsAndImports.getProperties() @@ -148,12 +259,12 @@ public CompletionStage> generateThingSkeleton(final ThingId thin return attributesBuilder.build(); }).ifPresent(builder::setAttributes); - return Pair.apply(thingModelWithExtensionsAndImports, builder); + return new AbstractMap.SimpleImmutableEntry<>(thingModelWithExtensionsAndImports, builder); }) .thenCompose(pair -> - createFeaturesFromSubmodels(pair.first(), generateDefaultsForOptionalProperties, dittoHeaders) + createFeaturesFromSubmodels(pair.getKey(), generateDefaultsForOptionalProperties, dittoHeaders) .thenApply(features -> - features.map(f -> pair.second().setFeatures(f)).orElse(pair.second()) + features.map(f -> pair.getValue().setFeatures(f)).orElse(pair.getValue()) ) ) .thenApply(builder -> Optional.of(builder.build())); @@ -209,15 +320,14 @@ private CompletionStage> createFeaturesFromSubmodels(final Th ).dittoHeaders(dittoHeaders) .build() ); - LOGGER.withCorrelationId(dittoHeaders) - .debug("Resolved TM submodel with instanceName <{}> and href <{}>", - instanceName, baseLink.getHref()); + LOGGER.debug("Resolved TM submodel with instanceName <{}> and href <{}>", + instanceName, baseLink.getHref()); return new Submodel(instanceName, baseLink.getHref()); } ) ) .orElseGet(Stream::empty) - .map(submodel -> thingModelFetcher.fetchThingModel(submodel.href, dittoHeaders) + .map(submodel -> thingModelResolver.resolveThingModel(submodel.href, dittoHeaders) .thenComposeAsync(subThingModel -> generateFeatureSkeleton(submodel.instanceName, subThingModel, @@ -268,19 +378,15 @@ public CompletionStage> generateFeatureSkeleton(final String f final boolean generateDefaultsForOptionalProperties, final DittoHeaders dittoHeaders) { - return thingModelExtensionResolver - .resolveThingModelExtensions(thingModel, dittoHeaders) - .thenCompose(thingModelWithExtensions -> thingModelExtensionResolver - .resolveThingModelRefs(thingModelWithExtensions, dittoHeaders)) + return CompletableFuture.completedFuture(thingModel) .thenCombine(resolveFeatureDefinition(thingModel, thingModelUrl, dittoHeaders), (thingModelWithExtensionsAndImports, featureDefinition) -> { final Optional dittoExtensionPrefix = thingModelWithExtensionsAndImports.getAtContext() .determinePrefixFor(DittoWotExtension.DITTO_WOT_EXTENSION); - LOGGER.withCorrelationId(dittoHeaders) - .debug("ThingModel for generating Feature skeleton after resolving extensions + refs: <{}>", - thingModelWithExtensionsAndImports); + LOGGER.debug("ThingModel for generating Feature skeleton after resolving extensions + refs: <{}>", + thingModelWithExtensionsAndImports); final FeatureBuilder.FromScratchBuildable builder = Feature.newBuilder(); thingModelWithExtensionsAndImports.getProperties() @@ -493,7 +599,7 @@ private CompletionStage> determineFurtherFeatureDefin if (extendsLink.isPresent()) { final BaseLink link = extendsLink.get(); - return thingModelFetcher.fetchThingModel(link.getHref(), dittoHeaders) + return thingModelResolver.resolveThingModel(link.getHref(), dittoHeaders) .thenComposeAsync(subThingModel -> determineFurtherFeatureDefinitionIdentifiers( // recurse! subThingModel, diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DittoWotExtension.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DittoWotExtension.java similarity index 91% rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DittoWotExtension.java rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DittoWotExtension.java index 26453f567b5..68a9fcfef56 100644 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/DittoWotExtension.java +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/DittoWotExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.wot.integration.generator; +package org.eclipse.ditto.wot.api.generator; import org.eclipse.ditto.wot.model.SingleUriAtContext; diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/WotThingDescriptionProvider.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/WotThingDescriptionGenerator.java similarity index 50% rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/WotThingDescriptionProvider.java rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/WotThingDescriptionGenerator.java index 3b7a7c83beb..706d25dcdd8 100644 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/WotThingDescriptionProvider.java +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/WotThingDescriptionGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,34 +10,32 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.wot.integration.provider; +package org.eclipse.ditto.wot.api.generator; -import java.util.Optional; +import java.net.URL; import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; import org.eclipse.ditto.base.model.headers.DittoHeaders; +import org.eclipse.ditto.json.JsonObject; import org.eclipse.ditto.things.model.Feature; -import org.eclipse.ditto.things.model.FeatureDefinition; import org.eclipse.ditto.things.model.Thing; import org.eclipse.ditto.things.model.ThingDefinition; import org.eclipse.ditto.things.model.ThingId; +import org.eclipse.ditto.wot.api.config.WotConfig; +import org.eclipse.ditto.wot.api.resolver.WotThingModelResolver; import org.eclipse.ditto.wot.model.ThingDescription; - -import org.apache.pekko.actor.ActorSystem; -import org.apache.pekko.actor.Extension; +import org.eclipse.ditto.wot.model.ThingModel; /** - * Extension for providing WoT (Web of Things) {@link ThingDescription}s for given {@code thingId}s from either a - * {@link ThingDefinition} or a {@link FeatureDefinition} from which the URL to a WoT - * {@link org.eclipse.ditto.wot.model.ThingModel} is resolved and the {@link ThingDescription} is provided. + * Generator for WoT (Web of Things) {@link ThingDescription} based on a given WoT {@link ThingModel} and context of the + * Ditto {@link Thing} to generate the ThingDescription for. * * @since 2.4.0 */ -@Immutable -public interface WotThingDescriptionProvider extends Extension { +public interface WotThingDescriptionGenerator { /** * Provides a {@link ThingDescription} for the given {@code thingDefinition} and {@code thingId} combination. @@ -78,44 +76,44 @@ CompletionStage provideFeatureTD(ThingId thingId, DittoHeaders dittoHeaders); /** - * Provides a {@link Thing} skeleton for the given {@code thingId} using the passed {@code thingDefinition} to - * extract a ThingModel URL from, fetching the ThingModel and generating the skeleton via - * {@link org.eclipse.ditto.wot.integration.generator.WotThingSkeletonGenerator}. - * The implementation should not throw exceptions, but return an empty optional if something went wrong during - * fetching or generation of the skeleton. + * Generates a ThingDescription for the given {@code thingId}, optionally using the passed {@code thing} to lookup + * thing specific placeholders. + * Uses the passed in {@code thingModel} and generates TD forms, security definition etc. in order to make it a + * valid TD. * - * @param thingId the ThingId to generate the Thing skeleton for. - * @param thingDefinition the ThingDefinition to resolve the ThingModel URL from. - * @param dittoHeaders the DittoHeaders for possibly thrown DittoRuntimeExceptions. - * @return an optional Thing skeleton or empty optional if something went wrong during the skeleton creation. + * @param thingId the ThingId to generate the ThingDescription for. + * @param thing the optional Thing from which to resolve metadata from. + * @param placeholderLookupObject the optional JsonObject to dynamically resolve placeholders from + * (e.g. a Thing or Feature). + * @param featureId the optional feature name if the TD should be generated for a certain feature of the Thing. + * @param thingModel the ThingModel to use as template for generating the TD. + * @param thingModelUrl the URL from which the ThingModel was fetched. + * @param dittoHeaders the DittoHeaders for possibly thrown DittoRuntimeException which might occur during the + * generation. + * @return the generated ThingDescription for the given {@code thingId} based on the passed in {@code thingModel}. + * @throws org.eclipse.ditto.wot.model.WotThingModelInvalidException if the WoT ThingModel did not contain the + * mandatory {@code "@type"} being {@code "tm:ThingModel"} */ - CompletionStage> provideThingSkeletonForCreation(ThingId thingId, - @Nullable ThingDefinition thingDefinition, - DittoHeaders dittoHeaders); - - /** - * Provides a {@link Feature} skeleton for the given {@code featureId} using the passed {@code featureDefinition} to - * extract a ThingModel URL from, fetching the ThingModel and generating the skeleton via - * {@link org.eclipse.ditto.wot.integration.generator.WotThingSkeletonGenerator}. - * The implementation should not throw exceptions, but return an empty optional if something went wrong during - * fetching or generation of the skeleton. - * - * @param featureId the FeatureId to generate the Feature skeleton for. - * @param featureDefinition the FeatureDefinition to resolve the ThingModel URL from. - * @param dittoHeaders the DittoHeaders for possibly thrown DittoRuntimeExceptions. - * @return an optional Feature skeleton or empty optional if something went wrong during the skeleton creation. - */ - CompletionStage> provideFeatureSkeletonForCreation(String featureId, - @Nullable FeatureDefinition featureDefinition, + CompletionStage generateThingDescription(ThingId thingId, + @Nullable Thing thing, + @Nullable JsonObject placeholderLookupObject, + @Nullable String featureId, + ThingModel thingModel, + URL thingModelUrl, DittoHeaders dittoHeaders); /** - * Get the {@code WotThingDescriptionProvider} for an actor system. + * Creates a new instance of WotThingDescriptionGenerator with the given {@code wotConfig}. * - * @param system the actor system. - * @return the {@code WotThingDescriptionProvider} extension. + * @param wotConfig the WoTConfig to use for creating the generator. + * @param thingModelResolver the ThingModel resolver to fetch and resolve (extensions, refs) of linked other + * ThingModels during the generation process. + * @param executor the executor to use to run async tasks. + * @return the created WotThingDescriptionGenerator. */ - static WotThingDescriptionProvider get(final ActorSystem system) { - return DefaultWotThingDescriptionProvider.ExtensionId.INSTANCE.get(system); + static WotThingDescriptionGenerator of(final WotConfig wotConfig, + final WotThingModelResolver thingModelResolver, + final Executor executor) { + return new DefaultWotThingDescriptionGenerator(wotConfig, thingModelResolver, executor); } } diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/WotThingModelExtensionResolver.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/WotThingModelExtensionResolver.java similarity index 87% rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/WotThingModelExtensionResolver.java rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/WotThingModelExtensionResolver.java index 38349e5409e..c0aee200dd3 100644 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/WotThingModelExtensionResolver.java +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/WotThingModelExtensionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,13 +10,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.wot.integration.generator; +package org.eclipse.ditto.wot.api.generator; import java.util.concurrent.CompletionStage; import java.util.concurrent.Executor; import org.eclipse.ditto.base.model.headers.DittoHeaders; -import org.eclipse.ditto.wot.integration.provider.WotThingModelFetcher; +import org.eclipse.ditto.wot.api.provider.WotThingModelFetcher; import org.eclipse.ditto.wot.model.ThingModel; /** @@ -60,13 +60,16 @@ public interface WotThingModelExtensionResolver { CompletionStage resolveThingModelRefs(ThingModel thingModel, DittoHeaders dittoHeaders); /** - * Creates a new instance of WotThingModelExtensionResolver with the given {@code thingModelFetcher}. + * Creates a new instance of WotThingModelExtensionResolver with the given {@code thingModelFetcher} and + * {@code executor}. * * @param thingModelFetcher the ThingModel fetcher to fetch linked other ThingModels during the generation process. - * @param executor the executor to use for async tasks. - * @return the created WotThingSkeletonGenerator. + * @param executor the executor to use to run async tasks. + * @return the created WotThingModelExtensionResolver. + * @since 3.6.0 */ - static WotThingModelExtensionResolver of(final WotThingModelFetcher thingModelFetcher, final Executor executor) { - return new DefaultWotThingModelExtensionResolver(thingModelFetcher, executor); + static WotThingModelExtensionResolver of(final WotThingModelFetcher thingModelFetcher, + final Executor executor) { + return new DefaultWotThingModelExtensionResolver(thingModelFetcher,executor); } } diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/WotThingSkeletonGenerator.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/WotThingSkeletonGenerator.java similarity index 69% rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/WotThingSkeletonGenerator.java rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/WotThingSkeletonGenerator.java index c9c724d5081..517bbadcf43 100644 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/WotThingSkeletonGenerator.java +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/WotThingSkeletonGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,18 +10,23 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.wot.integration.generator; +package org.eclipse.ditto.wot.api.generator; import java.net.URL; import java.util.Optional; import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; + +import javax.annotation.Nullable; -import org.apache.pekko.actor.ActorSystem; import org.eclipse.ditto.base.model.headers.DittoHeaders; import org.eclipse.ditto.things.model.Feature; +import org.eclipse.ditto.things.model.FeatureDefinition; import org.eclipse.ditto.things.model.Thing; +import org.eclipse.ditto.things.model.ThingDefinition; import org.eclipse.ditto.things.model.ThingId; -import org.eclipse.ditto.wot.integration.provider.WotThingModelFetcher; +import org.eclipse.ditto.wot.api.config.WotConfig; +import org.eclipse.ditto.wot.api.resolver.WotThingModelResolver; import org.eclipse.ditto.wot.model.ThingModel; /** @@ -57,6 +62,23 @@ default CompletionStage> generateThingSkeleton(ThingId thingId, return generateThingSkeleton(thingId, thingModel, thingModelUrl, false, dittoHeaders); } + /** + * Provides a {@link Thing} skeleton for the given {@code thingId} using the passed {@code thingDefinition} to + * extract a ThingModel URL from, fetching the ThingModel and generating the skeleton via + * {@link WotThingSkeletonGenerator}. + * The implementation should not throw exceptions, but return an empty optional if something went wrong during + * fetching or generation of the skeleton. + * + * @param thingId the ThingId to generate the Thing skeleton for. + * @param thingDefinition the ThingDefinition to resolve the ThingModel URL from. + * @param dittoHeaders the DittoHeaders for possibly thrown DittoRuntimeExceptions. + * @return an optional Thing skeleton or empty optional if something went wrong during the skeleton creation. + * @since 3.6.0 + */ + CompletionStage> provideThingSkeletonForCreation(ThingId thingId, + @Nullable ThingDefinition thingDefinition, + DittoHeaders dittoHeaders); + /** * Generates a skeleton {@link Thing} for the given {@code thingId}. * Uses the passed in {@code thingModel} and generates @@ -107,6 +129,23 @@ default CompletionStage> generateFeatureSkeleton(String featur return generateFeatureSkeleton(featureId, thingModel, thingModelUrl, false, dittoHeaders); } + /** + * Provides a {@link Feature} skeleton for the given {@code featureId} using the passed {@code featureDefinition} to + * extract a ThingModel URL from, fetching the ThingModel and generating the skeleton via + * {@link WotThingSkeletonGenerator}. + * The implementation should not throw exceptions, but return an empty optional if something went wrong during + * fetching or generation of the skeleton. + * + * @param featureId the FeatureId to generate the Feature skeleton for. + * @param featureDefinition the FeatureDefinition to resolve the ThingModel URL from. + * @param dittoHeaders the DittoHeaders for possibly thrown DittoRuntimeExceptions. + * @return an optional Feature skeleton or empty optional if something went wrong during the skeleton creation. + * @since 3.6.0 + */ + CompletionStage> provideFeatureSkeletonForCreation(String featureId, + @Nullable FeatureDefinition featureDefinition, + DittoHeaders dittoHeaders); + /** * Generates a skeleton {@link Feature} for the given {@code featureId}. * Uses the passed in {@code thingModel} and generates @@ -135,11 +174,15 @@ CompletionStage> generateFeatureSkeleton(String featureId, /** * Creates a new instance of WotThingSkeletonGenerator with the given {@code wotConfig}. * - * @param actorSystem the actor system to use. - * @param thingModelFetcher the ThingModel fetcher to fetch linked other ThingModels during the generation process. + * @param wotConfig the WoT Config to use for creating the generator. + * @param thingModelResolver the ThingModel resolver to fetch and resolve (extensions, refs) of linked other + * ThingModels during the generation process. + * @param executor the executor to use to run async tasks. * @return the created WotThingSkeletonGenerator. */ - static WotThingSkeletonGenerator of(final ActorSystem actorSystem, final WotThingModelFetcher thingModelFetcher) { - return new DefaultWotThingSkeletonGenerator(actorSystem, thingModelFetcher); + static WotThingSkeletonGenerator of(final WotConfig wotConfig, + final WotThingModelResolver thingModelResolver, + final Executor executor) { + return new DefaultWotThingSkeletonGenerator(wotConfig, thingModelResolver, executor); } } diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/package-info.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/package-info.java similarity index 79% rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/package-info.java rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/package-info.java index b37be650e03..bb7b5808abe 100644 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/package-info.java +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/generator/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -13,7 +13,7 @@ /** * The TM to TD Generator for the WoT (Web of Things) integration. - * @since 2.4.0 + * @since 3.6.0 */ @org.eclipse.ditto.utils.jsr305.annotations.AllParametersAndReturnValuesAreNonnullByDefault -package org.eclipse.ditto.wot.integration.generator; \ No newline at end of file +package org.eclipse.ditto.wot.api.generator; \ No newline at end of file diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/DefaultWotThingModelFetcher.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/DefaultWotThingModelFetcher.java new file mode 100644 index 00000000000..7bb4ba74200 --- /dev/null +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/DefaultWotThingModelFetcher.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.wot.api.provider; + +import java.net.MalformedURLException; +import java.net.URL; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nullable; + +import org.eclipse.ditto.base.model.headers.DittoHeaders; +import org.eclipse.ditto.internal.utils.cache.Cache; +import org.eclipse.ditto.internal.utils.cache.CacheFactory; +import org.eclipse.ditto.json.JsonObject; +import org.eclipse.ditto.wot.api.config.WotConfig; +import org.eclipse.ditto.wot.model.IRI; +import org.eclipse.ditto.wot.model.ThingDefinitionInvalidException; +import org.eclipse.ditto.wot.model.ThingModel; +import org.eclipse.ditto.wot.model.WotThingModelInvalidException; +import org.eclipse.ditto.wot.model.WotThingModelNotAccessibleException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.github.benmanes.caffeine.cache.AsyncCacheLoader; + +/** + * Default implementation of {@link WotThingModelFetcher} which should be not Ditto specific. + */ +final class DefaultWotThingModelFetcher implements WotThingModelFetcher { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultWotThingModelFetcher.class); + + private static final Duration MAX_FETCH_MODEL_DURATION = Duration.ofSeconds(10); + + private final JsonDownloader jsonDownloader; + private final Cache thingModelCache; + + DefaultWotThingModelFetcher(final WotConfig wotConfig, + final JsonDownloader jsonDownloader, + final Executor cacheLoaderExecutor) { + this.jsonDownloader = jsonDownloader; + final AsyncCacheLoader loader = this::loadThingModelViaHttp; + thingModelCache = CacheFactory.createCache(loader, + wotConfig.getCacheConfig(), + "ditto_wot_thing_model_cache", + cacheLoaderExecutor + ); + } + + @Override + public CompletableFuture fetchThingModel(final IRI iri, final DittoHeaders dittoHeaders) { + try { + return fetchThingModel(new URL(iri.toString()), dittoHeaders); + } catch (final MalformedURLException e) { + throw ThingDefinitionInvalidException.newBuilder(iri) + .dittoHeaders(dittoHeaders) + .build(); + } + } + + @Override + public CompletableFuture fetchThingModel(final URL url, final DittoHeaders dittoHeaders) { + LOGGER.debug("Fetching ThingModel (from cache or downloading as fallback) from URL: <{}>", url); + return thingModelCache.get(url) + .thenApply(optTm -> resolveThingModel(optTm.orElse(null), url, dittoHeaders)) + .orTimeout(MAX_FETCH_MODEL_DURATION.toSeconds(), TimeUnit.SECONDS); + } + + private ThingModel resolveThingModel(@Nullable final ThingModel thingModel, + final URL tmUrl, + final DittoHeaders dittoHeaders) { + if (null != thingModel) { + LOGGER.debug("Resolved ThingModel: <{}>", thingModel); + return thingModel; + } else { + throw WotThingModelNotAccessibleException.newBuilder(tmUrl) + .dittoHeaders(dittoHeaders) + .build(); + } + } + + /* this method is used to asynchronously load the ThingModel into the cache */ + private CompletableFuture loadThingModelViaHttp(final URL url, final Executor executor) { + LOGGER.debug("Loading ThingModel from URL <{}>.", url); + final CompletionStage responseFuture = jsonDownloader.downloadJsonViaHttp(url, executor); + final CompletionStage thingModelFuture = responseFuture + .thenApply(ThingModel::fromJson) + .exceptionally(t -> { + LOGGER.warn("Failed to extract ThingModel from response because of <{}: {}>", + t.getClass().getSimpleName(), t.getMessage()); + throw WotThingModelInvalidException.newBuilder(url) + .cause(t) + .build(); + }); + return thingModelFuture.toCompletableFuture(); + } + +} diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/JsonDownloader.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/JsonDownloader.java new file mode 100644 index 00000000000..0d94f486e02 --- /dev/null +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/JsonDownloader.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.wot.api.provider; + +import java.net.URL; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; + +import org.eclipse.ditto.json.JsonObject; + +/** + * Provides functionality to asynchronously download a {@link JsonObject} from a given {@link URL}. + * + * @since 3.6.0 + */ +public interface JsonDownloader { + + /** + * Downloads from the given {@code url} the content as {@code JsonObject} and provides it asynchronously using the + * passed {@code executor}. + * + * @param url the URL to download the json object from. + * @param executor the executor to use. + * @return a CompletionStage of the downloaded {@code JsonObject}, which may also be failed exceptionally, e.g. + * if the resource could not be accessed or of the provided resource could not be parsed as a {@link JsonObject}. + */ + CompletionStage downloadJsonViaHttp(URL url, Executor executor); +} diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/WotThingModelFetcher.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/WotThingModelFetcher.java similarity index 73% rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/WotThingModelFetcher.java rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/WotThingModelFetcher.java index bc9608a3c99..5c5023fcafa 100644 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/WotThingModelFetcher.java +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/WotThingModelFetcher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,14 +10,14 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.wot.integration.provider; +package org.eclipse.ditto.wot.api.provider; import java.net.URL; import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; -import org.apache.pekko.actor.ActorSystem; import org.eclipse.ditto.base.model.headers.DittoHeaders; -import org.eclipse.ditto.wot.integration.config.WotConfig; +import org.eclipse.ditto.wot.api.config.WotConfig; import org.eclipse.ditto.wot.model.IRI; import org.eclipse.ditto.wot.model.ThingModel; @@ -45,7 +45,7 @@ public interface WotThingModelFetcher { CompletionStage fetchThingModel(IRI iri, DittoHeaders dittoHeaders); /** - * Fetches the ThingModel resource at the passed {@code iurlri}. + * Fetches the ThingModel resource at the passed {@code url}. * * @param url the URL from which to fetch the ThingModel. * @param dittoHeaders the DittoHeaders for possibly thrown DittoRuntimeExceptions. @@ -58,13 +58,17 @@ public interface WotThingModelFetcher { CompletionStage fetchThingModel(URL url, DittoHeaders dittoHeaders); /** - * Creates a new instance of WotThingModelExtensionResolver with the given {@code actorSystem}. + * Creates a new instance of WotThingModelFetcher with the given {@code actorSystem} and {@code wotConfig}. * - * @param actorSystem the actor system to use. * @param wotConfig the WoTConfig to use for creating the generator. - * @return the created WotThingSkeletonGenerator. + * @param jsonDownloader the downloader to use to download a JsonObject from a given URL. + * @param cacheLoaderExecutor the executor to use to run async cache loading tasks. + * @return the created WotThingModelFetcher. + * @since 3.6.0 */ - static WotThingModelFetcher of(final ActorSystem actorSystem, final WotConfig wotConfig) { - return new DefaultWotThingModelFetcher(actorSystem, wotConfig); + static WotThingModelFetcher of(final WotConfig wotConfig, + final JsonDownloader jsonDownloader, + final Executor cacheLoaderExecutor) { + return new DefaultWotThingModelFetcher(wotConfig, jsonDownloader, cacheLoaderExecutor); } } diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/package-info.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/package-info.java similarity index 80% rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/package-info.java rename to wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/package-info.java index 0ab8bd7d389..e479d99dc33 100644 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/package-info.java +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/provider/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -13,7 +13,7 @@ /** * The provider for ThingDescriptions the WoT in the (Web of Things) integration. - * @since 2.4.0 + * @since 3.6.0 */ @org.eclipse.ditto.utils.jsr305.annotations.AllParametersAndReturnValuesAreNonnullByDefault -package org.eclipse.ditto.wot.integration.provider; \ No newline at end of file +package org.eclipse.ditto.wot.api.provider; \ No newline at end of file diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/DefaultWotThingModelResolver.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/DefaultWotThingModelResolver.java new file mode 100644 index 00000000000..bea8ec1dc92 --- /dev/null +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/DefaultWotThingModelResolver.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.wot.api.resolver; + +import java.net.MalformedURLException; +import java.net.URL; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nullable; + +import org.eclipse.ditto.base.model.headers.DittoHeaders; +import org.eclipse.ditto.internal.utils.cache.Cache; +import org.eclipse.ditto.internal.utils.cache.CacheFactory; +import org.eclipse.ditto.wot.api.config.WotConfig; +import org.eclipse.ditto.wot.api.generator.WotThingModelExtensionResolver; +import org.eclipse.ditto.wot.api.provider.WotThingModelFetcher; +import org.eclipse.ditto.wot.model.IRI; +import org.eclipse.ditto.wot.model.ThingDefinitionInvalidException; +import org.eclipse.ditto.wot.model.ThingModel; +import org.eclipse.ditto.wot.model.WotThingModelNotAccessibleException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.github.benmanes.caffeine.cache.AsyncCacheLoader; + +/** + * Default implementation of {@link WotThingModelResolver} which should be not Ditto specific. + */ +final class DefaultWotThingModelResolver implements WotThingModelResolver { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultWotThingModelResolver.class); + + private static final Duration MAX_RESOLVE_MODEL_DURATION = Duration.ofSeconds(12); + + private final WotThingModelFetcher thingModelFetcher; + private final WotThingModelExtensionResolver thingModelExtensionResolver; + private final Cache fullyResolvedThingModelCache; + + DefaultWotThingModelResolver(final WotConfig wotConfig, + final WotThingModelFetcher thingModelFetcher, + final WotThingModelExtensionResolver thingModelExtensionResolver, + final Executor cacheLoaderExecutor) { + this.thingModelFetcher = thingModelFetcher; + this.thingModelExtensionResolver = thingModelExtensionResolver; + final AsyncCacheLoader loader = this::loadThingModelViaHttp; + fullyResolvedThingModelCache = CacheFactory.createCache(loader, + wotConfig.getCacheConfig(), + "ditto_wot_fully_resolved_thing_model_cache", + cacheLoaderExecutor); + } + + @Override + public CompletableFuture resolveThingModel(final IRI iri, final DittoHeaders dittoHeaders) { + try { + return resolveThingModel(new URL(iri.toString()), dittoHeaders); + } catch (final MalformedURLException e) { + throw ThingDefinitionInvalidException.newBuilder(iri) + .dittoHeaders(dittoHeaders) + .build(); + } + } + + @Override + public CompletableFuture resolveThingModel(final URL url, final DittoHeaders dittoHeaders) { + LOGGER.debug("Resolving ThingModel (from cache or downloading as fallback) from URL: <{}>", url); + return fullyResolvedThingModelCache.get(url) + .thenApply(optTm -> resolveThingModel(optTm.orElse(null), url, dittoHeaders)) + .orTimeout(MAX_RESOLVE_MODEL_DURATION.toSeconds(), TimeUnit.SECONDS); + } + + private ThingModel resolveThingModel(@Nullable final ThingModel thingModel, + final URL tmUrl, + final DittoHeaders dittoHeaders) { + if (null != thingModel) { + LOGGER.debug("Fully Resolved ThingModel: <{}>", thingModel); + return thingModel; + } else { + throw WotThingModelNotAccessibleException.newBuilder(tmUrl) + .dittoHeaders(dittoHeaders) + .build(); + } + } + + /* this method is used to asynchronously load the ThingModel into the cache */ + private CompletableFuture loadThingModelViaHttp(final URL url, final Executor executor) { + LOGGER.debug("Loading ThingModel from URL <{}>.", url); + final DittoHeaders dittoHeaders = DittoHeaders.empty(); + return thingModelFetcher.fetchThingModel(url, dittoHeaders) + .thenComposeAsync(thingModel -> + thingModelExtensionResolver + .resolveThingModelExtensions(thingModel, dittoHeaders) + .thenCompose(thingModelWithExtensions -> + thingModelExtensionResolver.resolveThingModelRefs(thingModelWithExtensions, + dittoHeaders) + ), + executor + ) + .toCompletableFuture(); + } + +} diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/WotThingModelResolver.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/WotThingModelResolver.java new file mode 100644 index 00000000000..2b3c3b19c4e --- /dev/null +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/WotThingModelResolver.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.wot.api.resolver; + +import java.net.URL; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; + +import org.eclipse.ditto.base.model.headers.DittoHeaders; +import org.eclipse.ditto.wot.api.config.WotConfig; +import org.eclipse.ditto.wot.api.generator.WotThingModelExtensionResolver; +import org.eclipse.ditto.wot.api.provider.WotThingModelFetcher; +import org.eclipse.ditto.wot.model.IRI; +import org.eclipse.ditto.wot.model.ThingModel; + +/** + * Fetches WoT (Web of Things) ThingModels from {@code IRI}s/{@code URL}s, resolves extensions and references and + * caches the fully resolved ThingModel in order to not always resolve extensions and references. + * + * @since 3.6.0 + */ +public interface WotThingModelResolver { + + /** + * Fetches the ThingModel resource at the passed {@code iri} and resolves extensions and references, returning the + * fully resolved model. + * + * @param iri the IRI (URL) from which to fetch the ThingModel. + * @param dittoHeaders the DittoHeaders for possibly thrown DittoRuntimeExceptions. + * @return a CompletionStage containing the fetched ThingModel or completed exceptionally with a + * {@link org.eclipse.ditto.wot.model.WotThingModelInvalidException} if the fetched ThingModel could not be + * parsed/interpreted as correct WoT ThingModel. + * @throws org.eclipse.ditto.wot.model.ThingDefinitionInvalidException if the passed {@code iri} did not contain a + * valid URL. + * @throws org.eclipse.ditto.wot.model.WotThingModelNotAccessibleException if the ThingModel could not be + * fetched at the given {@code iri}. + */ + CompletionStage resolveThingModel(IRI iri, DittoHeaders dittoHeaders); + + /** + * Fetches the ThingModel resource at the passed {@code url} and resolves extensions and references, returning the + * fully resolved model. + * + * @param url the URL from which to fetch the ThingModel. + * @param dittoHeaders the DittoHeaders for possibly thrown DittoRuntimeExceptions. + * @return a CompletionStage containing the fetched ThingModel or completed exceptionally with a + * {@link org.eclipse.ditto.wot.model.WotThingModelInvalidException} if the fetched ThingModel could not be + * parsed/interpreted as correct WoT ThingModel. + * @throws org.eclipse.ditto.wot.model.WotThingModelNotAccessibleException if the ThingModel could not be + * fetched at the given {@code url}. + */ + CompletionStage resolveThingModel(URL url, DittoHeaders dittoHeaders); + + /** + * Creates a new instance of WotThingModelResolver with the given {@code actorSystem} and {@code wotConfig}. + * + * @param wotConfig the WoT Config to use for creating the resolver. + * @param thingModelFetcher the WoT ThingModel fetcher used to download/fetch TMs from URLs. + * @param thingModelExtensionResolver the WoT ThingModel extension and reference resolver used to resolve + * {@code tm:extends} and {@code tm:ref} constructs in ThingModels. + * @param cacheLoaderExecutor the executor to use to run async cache loading tasks. + * @return the created WotThingModelResolver. + * @since 3.6.0 + */ + static WotThingModelResolver of(final WotConfig wotConfig, + final WotThingModelFetcher thingModelFetcher, + final WotThingModelExtensionResolver thingModelExtensionResolver, + final Executor cacheLoaderExecutor) { + return new DefaultWotThingModelResolver(wotConfig, thingModelFetcher, thingModelExtensionResolver, + cacheLoaderExecutor); + } +} diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/package-info.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/package-info.java new file mode 100644 index 00000000000..8679aa4d8a4 --- /dev/null +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/resolver/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +/** + * The resolve for loading and resolving (extensions and references in) ThingModels the WoT in the (Web of Things) + * integration. + * @since 3.6.0 + */ +@org.eclipse.ditto.utils.jsr305.annotations.AllParametersAndReturnValuesAreNonnullByDefault +package org.eclipse.ditto.wot.api.resolver; \ No newline at end of file diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/DefaultWotThingModelValidator.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/DefaultWotThingModelValidator.java new file mode 100644 index 00000000000..4e50d6c554c --- /dev/null +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/DefaultWotThingModelValidator.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.wot.api.validator; + +import java.net.URL; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; +import java.util.function.Function; + +import javax.annotation.concurrent.Immutable; + +import org.eclipse.ditto.base.model.headers.DittoHeaders; +import org.eclipse.ditto.base.model.signals.FeatureToggle; +import org.eclipse.ditto.things.model.DefinitionIdentifier; +import org.eclipse.ditto.things.model.Feature; +import org.eclipse.ditto.things.model.FeatureDefinition; +import org.eclipse.ditto.things.model.Thing; +import org.eclipse.ditto.wot.api.config.WotConfig; +import org.eclipse.ditto.wot.api.resolver.WotThingModelResolver; +import org.eclipse.ditto.wot.model.ThingModel; +import org.eclipse.ditto.wot.model.ThingSkeleton; +import org.eclipse.ditto.wot.validation.WotThingModelValidation; + +/** + * Default Ditto specific implementation of {@link WotThingModelValidator}. + */ +@Immutable +final class DefaultWotThingModelValidator implements WotThingModelValidator { + + private final WotConfig wotConfig; + private final WotThingModelResolver thingModelResolver; + private final WotThingModelValidation thingModelValidation; + private final Executor executor; + + DefaultWotThingModelValidator(final WotConfig wotConfig, + final WotThingModelResolver thingModelResolver, + final Executor executor) { + this.wotConfig = wotConfig; + this.thingModelResolver = thingModelResolver; + thingModelValidation = WotThingModelValidation.createInstance(wotConfig.getValidationConfig()); + this.executor = executor; + } + + @Override + public CompletionStage validateThing(final Thing thing, final DittoHeaders dittoHeaders) { + + if (FeatureToggle.isWotIntegrationFeatureEnabled() && wotConfig.getValidationConfig().isEnabled()) { + final Optional urlOpt = thing.getDefinition().flatMap(DefinitionIdentifier::getUrl); + if (urlOpt.isPresent()) { + final URL url = urlOpt.get(); + final Function> validationFunction = + thingModelWithExtensionsAndImports -> + validateThing(thingModelWithExtensionsAndImports, thing, dittoHeaders); + return fetchResolveAndValidateWith(url, dittoHeaders, validationFunction); + } else { + return CompletableFuture.completedStage(null); + } + } else { + return CompletableFuture.completedStage(null); + } + } + + @Override + public CompletionStage validateThing(final ThingSkeleton thingSkeleton, + final Thing thing, + final DittoHeaders dittoHeaders) { + + if (FeatureToggle.isWotIntegrationFeatureEnabled() && wotConfig.getValidationConfig().isEnabled()) { + return thingModelValidation.validateThing(thingSkeleton, thing, dittoHeaders); + } else { + return CompletableFuture.completedStage(null); + } + } + + @Override + public CompletionStage validateFeature(final Feature feature, final DittoHeaders dittoHeaders) { + + if (FeatureToggle.isWotIntegrationFeatureEnabled() && wotConfig.getValidationConfig().isEnabled()) { + final Optional definitionIdentifier = feature.getDefinition() + .map(FeatureDefinition::getFirstIdentifier); + final Optional urlOpt = definitionIdentifier.flatMap(DefinitionIdentifier::getUrl); + if (urlOpt.isPresent()) { + final URL url = urlOpt.get(); + final Function> validationFunction = + thingModelWithExtensionsAndImports -> + validateFeature(thingModelWithExtensionsAndImports, feature, dittoHeaders); + return fetchResolveAndValidateWith(url, dittoHeaders, validationFunction); + } else { + return CompletableFuture.completedStage(null); + } + } else { + return CompletableFuture.completedStage(null); + } + } + + @Override + public CompletionStage validateFeature(final ThingSkeleton thingSkeleton, final Feature feature, + final DittoHeaders dittoHeaders) { + + if (FeatureToggle.isWotIntegrationFeatureEnabled() && wotConfig.getValidationConfig().isEnabled()) { + return thingModelValidation.validateFeature(thingSkeleton, feature, dittoHeaders); + } else { + return CompletableFuture.completedStage(null); + } + } + + private CompletionStage fetchResolveAndValidateWith(final URL url, + final DittoHeaders dittoHeaders, + final Function> validationFunction) { + + return thingModelResolver.resolveThingModel(url, dittoHeaders) + .thenComposeAsync(validationFunction, executor); + } +} diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/WotThingModelValidator.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/WotThingModelValidator.java new file mode 100644 index 00000000000..3c5bd5b9a07 --- /dev/null +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/WotThingModelValidator.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.wot.api.validator; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; + +import org.eclipse.ditto.base.model.headers.DittoHeaders; +import org.eclipse.ditto.things.model.Feature; +import org.eclipse.ditto.things.model.Thing; +import org.eclipse.ditto.wot.api.config.WotConfig; +import org.eclipse.ditto.wot.api.resolver.WotThingModelResolver; +import org.eclipse.ditto.wot.model.ThingSkeleton; + +/** + * TODO TJ doc + * @since 3.6.0 + */ +public interface WotThingModelValidator { + + /** + * TODO TJ doc + */ + CompletionStage validateThing(Thing thing, DittoHeaders dittoHeaders); + + /** + * TODO TJ doc + */ + CompletionStage validateThing(ThingSkeleton thingSkeleton, Thing thing, DittoHeaders dittoHeaders); + + /** + * TODO TJ doc + */ + CompletionStage validateFeature(Feature feature, DittoHeaders dittoHeaders); + + /** + * TODO TJ doc + */ + CompletionStage validateFeature(ThingSkeleton thingSkeleton, Feature feature, DittoHeaders dittoHeaders); + + /** + * Creates a new instance of WotThingModelValidator with the given {@code wotConfig}. + * + * @param wotConfig the WoT config to use. + * @param thingModelResolver the ThingModel resolver to fetch and resolve (extensions, refs) of linked other + * ThingModels during the generation process. + * @param executor the executor to use to run async tasks. + * @return the created WotThingModelValidator. + */ + static WotThingModelValidator of(final WotConfig wotConfig, + final WotThingModelResolver thingModelResolver, + final Executor executor) { + return new DefaultWotThingModelValidator(wotConfig, thingModelResolver, executor); + } +} diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/package-info.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/package-info.java new file mode 100644 index 00000000000..236a0d49dc0 --- /dev/null +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/package-info.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +/** + * @since 3.6.0 + */ +@org.eclipse.ditto.utils.jsr305.annotations.AllParametersAndReturnValuesAreNonnullByDefault +package org.eclipse.ditto.wot.api.validator; \ No newline at end of file diff --git a/wot/integration/pom.xml b/wot/integration/pom.xml index d1552409c04..44146afb071 100755 --- a/wot/integration/pom.xml +++ b/wot/integration/pom.xml @@ -34,10 +34,18 @@ org.eclipse.ditto ditto-base-model + + org.eclipse.ditto + ditto-wot-api + org.eclipse.ditto ditto-wot-model + + org.eclipse.ditto + ditto-wot-validation + org.eclipse.ditto ditto-things-model @@ -51,12 +59,6 @@ org.eclipse.ditto ditto-internal-utils-http - - - - - - diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/DefaultDittoWotIntegration.java b/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/DefaultDittoWotIntegration.java new file mode 100644 index 00000000000..1230eeac3ab --- /dev/null +++ b/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/DefaultDittoWotIntegration.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.wot.integration; + +import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull; + +import java.util.concurrent.Executor; + +import javax.annotation.concurrent.Immutable; + +import org.apache.pekko.actor.AbstractExtensionId; +import org.apache.pekko.actor.ActorSystem; +import org.apache.pekko.actor.ExtendedActorSystem; +import org.eclipse.ditto.internal.utils.config.DefaultScopedConfig; +import org.eclipse.ditto.internal.utils.pekko.logging.DittoLoggerFactory; +import org.eclipse.ditto.internal.utils.pekko.logging.ThreadSafeDittoLogger; +import org.eclipse.ditto.wot.api.config.DefaultWotConfig; +import org.eclipse.ditto.wot.api.config.WotConfig; +import org.eclipse.ditto.wot.api.generator.WotThingDescriptionGenerator; +import org.eclipse.ditto.wot.api.generator.WotThingModelExtensionResolver; +import org.eclipse.ditto.wot.api.generator.WotThingSkeletonGenerator; +import org.eclipse.ditto.wot.api.provider.WotThingModelFetcher; +import org.eclipse.ditto.wot.api.resolver.WotThingModelResolver; +import org.eclipse.ditto.wot.api.validator.WotThingModelValidator; + +/** + * Default Ditto specific implementation of {@link DittoWotIntegration} and Pekko extension. + */ +@Immutable +final class DefaultDittoWotIntegration implements DittoWotIntegration { + + private static final ThreadSafeDittoLogger LOGGER = + DittoLoggerFactory.getThreadSafeLogger(DefaultDittoWotIntegration.class); + + private final WotConfig wotConfig; + private final WotThingModelFetcher thingModelFetcher; + private final WotThingModelExtensionResolver thingModelExtensionResolver; + private final WotThingModelResolver thingModelResolver; + private final WotThingDescriptionGenerator thingDescriptionGenerator; + private final WotThingSkeletonGenerator thingSkeletonGenerator; + private final WotThingModelValidator thingModelValidator; + + private DefaultDittoWotIntegration(final ActorSystem actorSystem, final WotConfig wotConfig) { + this.wotConfig = checkNotNull(wotConfig, "wotConfig"); + LOGGER.info("Initializing DefaultDittoWotIntegration with config: {}", wotConfig); + + final Executor executor = actorSystem.dispatchers().lookup("wot-dispatcher"); + final Executor cacheLoaderExecutor = actorSystem.dispatchers().lookup("wot-dispatcher-cache-loader"); + final PekkoHttpJsonDownloader httpThingModelDownloader = + new PekkoHttpJsonDownloader(actorSystem, wotConfig); + thingModelFetcher = WotThingModelFetcher.of(wotConfig, httpThingModelDownloader, cacheLoaderExecutor); + thingModelExtensionResolver = WotThingModelExtensionResolver.of(thingModelFetcher, executor); + thingModelResolver = + WotThingModelResolver.of(wotConfig, thingModelFetcher, thingModelExtensionResolver, cacheLoaderExecutor); + thingDescriptionGenerator = WotThingDescriptionGenerator.of(wotConfig, thingModelResolver, executor); + thingSkeletonGenerator = WotThingSkeletonGenerator.of(wotConfig, thingModelResolver, executor); + thingModelValidator = WotThingModelValidator.of(wotConfig, thingModelResolver, executor); + } + + /** + * Returns a new {@code DefaultWotThingDescriptionProvider} for the given parameters. + * + * @param actorSystem the actor system to use. + * @param wotConfig the WoT config to use. + * @return the DefaultWotThingDescriptionProvider. + * @throws NullPointerException if any argument is {@code null}. + */ + public static DefaultDittoWotIntegration of(final ActorSystem actorSystem, final WotConfig wotConfig) { + return new DefaultDittoWotIntegration(actorSystem, wotConfig); + } + + @Override + public WotConfig getWotConfig() { + return wotConfig; + } + + @Override + public WotThingModelFetcher getWotThingModelFetcher() { + return thingModelFetcher; + } + + @Override + public WotThingModelResolver getWotThingModelResolver() { + return thingModelResolver; + } + + @Override + public WotThingDescriptionGenerator getWotThingDescriptionGenerator() { + return thingDescriptionGenerator; + } + + @Override + public WotThingModelExtensionResolver getWotThingModelExtensionResolver() { + return thingModelExtensionResolver; + } + + @Override + public WotThingSkeletonGenerator getWotThingSkeletonGenerator() { + return thingSkeletonGenerator; + } + + @Override + public WotThingModelValidator getWotThingModelValidator() { + return thingModelValidator; + } + + + static final class ExtensionId extends AbstractExtensionId { + + static final ExtensionId INSTANCE = new ExtensionId(); + + private ExtensionId() {} + + @Override + public DittoWotIntegration createExtension(final ExtendedActorSystem system) { + final WotConfig wotConfig = DefaultWotConfig.of( + DefaultScopedConfig.dittoScoped(system.settings().config()) + .getConfig(DefaultWotConfig.WOT_PARENT_CONFIG_PATH) + ); + return of(system, wotConfig); + } + } + +} diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/DittoWotIntegration.java b/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/DittoWotIntegration.java new file mode 100644 index 00000000000..dc912b92148 --- /dev/null +++ b/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/DittoWotIntegration.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.wot.integration; + +import org.apache.pekko.actor.ActorSystem; +import org.apache.pekko.actor.Extension; +import org.eclipse.ditto.wot.api.config.WotConfig; +import org.eclipse.ditto.wot.api.generator.WotThingDescriptionGenerator; +import org.eclipse.ditto.wot.api.generator.WotThingModelExtensionResolver; +import org.eclipse.ditto.wot.api.generator.WotThingSkeletonGenerator; +import org.eclipse.ditto.wot.api.provider.WotThingModelFetcher; +import org.eclipse.ditto.wot.api.resolver.WotThingModelResolver; +import org.eclipse.ditto.wot.api.validator.WotThingModelValidator; + +/** + * Extension providing access to all Ditto WoT integration capabilities. + * + * @since 3.6.0 + */ +public interface DittoWotIntegration extends Extension { + + /** + * @return the applied WoT configuration. + */ + WotConfig getWotConfig(); + + /** + * @return the WoT ThingModel fetcher used to download/fetch TMs from URLs. + */ + WotThingModelFetcher getWotThingModelFetcher(); + + /** + * @return the WoT ThingModel resolver which fetches ThingModels and in addition resolves extensions + * and reference to other ThingModels. + */ + WotThingModelResolver getWotThingModelResolver(); + + /** + * @return the WoT ThingDescription generator which generates ThingDescriptions based on ThingModels. + */ + WotThingDescriptionGenerator getWotThingDescriptionGenerator(); + + /** + * @return the WoT ThingModel extension and reference resolver used to resolve {@code tm:extends} and + * {@code tm:ref} constructs in ThingModels. + */ + WotThingModelExtensionResolver getWotThingModelExtensionResolver(); + + /** + * @return the WoT Thing skeleton generator which generates a JSON skeleton when creating new things + * or features based on a ThingModel, adhering to default values of the model. + */ + WotThingSkeletonGenerator getWotThingSkeletonGenerator(); + + /** + * @return the WoT ThingModel validator which can validate and enforce Ditto Thing payloads based on the + * defined ThingModel and its JsonSchema for properties, actions, events. + */ + WotThingModelValidator getWotThingModelValidator(); + + /** + * Get the {@code DittoWotIntegration} for an actor system. + * + * @param system the actor system. + * @return the {@code DittoWotIntegration} extension. + */ + static DittoWotIntegration get(final ActorSystem system) { + return DefaultDittoWotIntegration.ExtensionId.INSTANCE.get(system); + } +} diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/DefaultWotThingModelFetcher.java b/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/PekkoHttpJsonDownloader.java similarity index 56% rename from wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/DefaultWotThingModelFetcher.java rename to wot/integration/src/main/java/org/eclipse/ditto/wot/integration/PekkoHttpJsonDownloader.java index 765897006ab..342d184ce9c 100644 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/DefaultWotThingModelFetcher.java +++ b/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/PekkoHttpJsonDownloader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -10,39 +10,15 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.ditto.wot.integration.provider; +package org.eclipse.ditto.wot.integration; import java.net.MalformedURLException; import java.net.URL; import java.text.MessageFormat; -import java.time.Duration; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; - -import javax.annotation.Nullable; - -import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException; -import org.eclipse.ditto.base.model.headers.DittoHeaders; -import org.eclipse.ditto.internal.utils.pekko.logging.DittoLoggerFactory; -import org.eclipse.ditto.internal.utils.pekko.logging.ThreadSafeDittoLogger; -import org.eclipse.ditto.internal.utils.cache.Cache; -import org.eclipse.ditto.internal.utils.cache.CacheFactory; -import org.eclipse.ditto.internal.utils.http.DefaultHttpClientFacade; -import org.eclipse.ditto.internal.utils.http.HttpClientFacade; -import org.eclipse.ditto.json.JsonFactory; -import org.eclipse.ditto.json.JsonObject; -import org.eclipse.ditto.json.JsonValue; -import org.eclipse.ditto.wot.integration.config.WotConfig; -import org.eclipse.ditto.wot.model.IRI; -import org.eclipse.ditto.wot.model.ThingDefinitionInvalidException; -import org.eclipse.ditto.wot.model.ThingModel; -import org.eclipse.ditto.wot.model.WotThingModelInvalidException; -import org.eclipse.ditto.wot.model.WotThingModelNotAccessibleException; - -import com.github.benmanes.caffeine.cache.AsyncCacheLoader; import org.apache.pekko.actor.ActorSystem; import org.apache.pekko.http.javadsl.model.HttpHeader; @@ -56,16 +32,25 @@ import org.apache.pekko.stream.SystemMaterializer; import org.apache.pekko.stream.javadsl.Sink; import org.apache.pekko.util.ByteString; +import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException; +import org.eclipse.ditto.internal.utils.http.DefaultHttpClientFacade; +import org.eclipse.ditto.internal.utils.http.HttpClientFacade; +import org.eclipse.ditto.internal.utils.pekko.logging.DittoLoggerFactory; +import org.eclipse.ditto.internal.utils.pekko.logging.ThreadSafeDittoLogger; +import org.eclipse.ditto.json.JsonFactory; +import org.eclipse.ditto.json.JsonObject; +import org.eclipse.ditto.json.JsonValue; +import org.eclipse.ditto.wot.api.config.WotConfig; +import org.eclipse.ditto.wot.api.provider.JsonDownloader; +import org.eclipse.ditto.wot.model.WotThingModelNotAccessibleException; /** - * Default implementation of {@link WotThingModelFetcher} which should be not Ditto specific. + * Pekko HTTP based implementation of {@link JsonDownloader}. */ -final class DefaultWotThingModelFetcher implements WotThingModelFetcher { +final class PekkoHttpJsonDownloader implements JsonDownloader { private static final ThreadSafeDittoLogger LOGGER = - DittoLoggerFactory.getThreadSafeLogger(DefaultWotThingModelFetcher.class); - - private static final Duration MAX_FETCH_MODEL_DURATION = Duration.ofSeconds(10); + DittoLoggerFactory.getThreadSafeLogger(PekkoHttpJsonDownloader.class); private static final HttpHeader ACCEPT_HEADER = Accept.create( MediaRanges.create(MediaTypes.applicationWithOpenCharset("tm+json")), @@ -74,61 +59,21 @@ final class DefaultWotThingModelFetcher implements WotThingModelFetcher { private final HttpClientFacade httpClient; private final Materializer materializer; - private final Cache thingModelCache; - DefaultWotThingModelFetcher(final ActorSystem actorSystem, final WotConfig wotConfig) { + PekkoHttpJsonDownloader(final ActorSystem actorSystem, final WotConfig wotConfig) { this.httpClient = DefaultHttpClientFacade.getInstance(actorSystem, wotConfig.getHttpProxyConfig()); materializer = SystemMaterializer.get(actorSystem).materializer(); - final AsyncCacheLoader loader = this::loadThingModelViaHttp; - thingModelCache = CacheFactory.createCache(loader, - wotConfig.getCacheConfig(), - "ditto_wot_thing_model_cache", - actorSystem.dispatchers().lookup("wot-dispatcher-cache-loader")); - } - - @Override - public CompletableFuture fetchThingModel(final IRI iri, final DittoHeaders dittoHeaders) { - try { - return fetchThingModel(new URL(iri.toString()), dittoHeaders); - } catch (final MalformedURLException e) { - throw ThingDefinitionInvalidException.newBuilder(iri) - .dittoHeaders(dittoHeaders) - .build(); - } } @Override - public CompletableFuture fetchThingModel(final URL url, final DittoHeaders dittoHeaders) { - LOGGER.withCorrelationId(dittoHeaders) - .debug("Fetching ThingModel (from cache or downloading as fallback) from URL: <{}>", url); - return thingModelCache.get(url) - .thenApply(optTm -> resolveThingModel(optTm.orElse(null), url, dittoHeaders)) - .orTimeout(MAX_FETCH_MODEL_DURATION.toSeconds(), TimeUnit.SECONDS); - } - - private ThingModel resolveThingModel(@Nullable final ThingModel thingModel, - final URL tmUrl, - final DittoHeaders dittoHeaders) { - if (null != thingModel) { - LOGGER.withCorrelationId(dittoHeaders).debug("Resolved ThingModel: <{}>", thingModel); - return thingModel; - } else { - throw WotThingModelNotAccessibleException.newBuilder(tmUrl) - .dittoHeaders(dittoHeaders) - .build(); - } - } - - /* this method is used to asynchronously load the ThingModel into the cache */ - private CompletableFuture loadThingModelViaHttp(final URL url, final Executor executor) { - LOGGER.debug("Loading ThingModel from URL <{}>.", url); - final CompletionStage responseFuture = getThingModelFromUrl(url); - final CompletionStage thingModelFuture = responseFuture.thenCompose( - response -> mapResponseToThingModel(response, url)); + public CompletionStage downloadJsonViaHttp(final URL url, final Executor executor) { + LOGGER.debug("Loading JsonObject from URL <{}>.", url); + final CompletionStage responseFuture = getJsonObjectFromUrl(url); + final CompletionStage thingModelFuture = responseFuture.thenCompose(this::mapResponseToJsonObject); return thingModelFuture.toCompletableFuture(); } - private CompletionStage getThingModelFromUrl(final URL url) { + private CompletionStage getJsonObjectFromUrl(final URL url) { return httpClient.createSingleHttpRequest(HttpRequest.GET(url.toString()).withHeaders(List.of(ACCEPT_HEADER))) .thenCompose(response -> { if (response.status().isRedirection()) { @@ -142,7 +87,7 @@ private CompletionStage getThingModelFromUrl(final URL url) { cause -> handleUnexpectedException(cause, url)); } }) - .map(this::getThingModelFromUrl) // recurse following the redirect + .map(this::getJsonObjectFromUrl) // recurse following the redirect .orElseGet(() -> CompletableFuture.completedFuture(response)); } else { return CompletableFuture.completedFuture(response); @@ -160,20 +105,6 @@ private CompletionStage getThingModelFromUrl(final URL url) { }); } - private CompletableFuture mapResponseToThingModel(final HttpResponse response, final URL url) { - final CompletableFuture bodyFuture = mapResponseToJsonObject(response) - .toCompletableFuture(); - return bodyFuture - .thenApply(ThingModel::fromJson) - .exceptionally(t -> { - LOGGER.warn("Failed to extract ThingModel from response <{}> because of <{}: {}>", response, - t.getClass().getSimpleName(), t.getMessage()); - throw WotThingModelInvalidException.newBuilder(url) - .cause(t) - .build(); - }); - } - private CompletionStage mapResponseToJsonObject(final HttpResponse response) { return response.entity().getDataBytes().fold(ByteString.emptyByteString(), ByteString::concat) .map(ByteString::utf8String) @@ -184,7 +115,7 @@ private CompletionStage mapResponseToJsonObject(final HttpResponse r private void handleNonSuccessResponse(final HttpResponse response, final URL url) { final String msg = MessageFormat.format( - "Got non success response from ThingModel endpoint with status code: <{0}>", response.status()); + "Got non success response from JsonObject endpoint with status code: <{0}>", response.status()); getBodyAsString(response) .thenAccept(stringBody -> LOGGER.info("{} and body: <{}>.", msg, stringBody)); throw WotThingModelNotAccessibleException.newBuilder(url) @@ -199,7 +130,7 @@ private CompletionStage getBodyAsString(final HttpResponse response) { private static DittoRuntimeException handleUnexpectedException(final Throwable e, final URL url) { - final String msg = MessageFormat.format("Got Exception from ThingModel endpoint <{0}>.", url); + final String msg = MessageFormat.format("Got Exception from JsonObject endpoint <{0}>.", url); LOGGER.warn(msg, e); throw WotThingModelNotAccessibleException.newBuilder(url) .cause(e) diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/WotThingDescriptionGenerator.java b/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/WotThingDescriptionGenerator.java deleted file mode 100644 index b3eb6ad9ebc..00000000000 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/generator/WotThingDescriptionGenerator.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipse.ditto.wot.integration.generator; - -import java.net.URL; -import java.util.concurrent.CompletionStage; - -import javax.annotation.Nullable; - -import org.eclipse.ditto.base.model.headers.DittoHeaders; -import org.eclipse.ditto.json.JsonObject; -import org.eclipse.ditto.things.model.Thing; -import org.eclipse.ditto.things.model.ThingId; -import org.eclipse.ditto.wot.integration.config.WotConfig; -import org.eclipse.ditto.wot.integration.provider.WotThingModelFetcher; -import org.eclipse.ditto.wot.model.ThingDescription; -import org.eclipse.ditto.wot.model.ThingModel; - -import org.apache.pekko.actor.ActorSystem; - -/** - * Generator for WoT (Web of Things) {@link ThingDescription} based on a given WoT {@link ThingModel} and context of the - * Ditto {@link Thing} to generate the ThingDescription for. - * - * @since 2.4.0 - */ -public interface WotThingDescriptionGenerator { - - /** - * Generates a ThingDescription for the given {@code thingId}, optionally using the passed {@code thing} to lookup - * thing specific placeholders. - * Uses the passed in {@code thingModel} and generates TD forms, security definition etc. in order to make it a - * valid TD. - * - * @param thingId the ThingId to generate the ThingDescription for. - * @param thing the optional Thing from which to resolve metadata from. - * @param placeholderLookupObject the optional JsonObject to dynamically resolve placeholders from - * (e.g. a Thing or Feature). - * @param featureId the optional feature name if the TD should be generated for a certain feature of the Thing. - * @param thingModel the ThingModel to use as template for generating the TD. - * @param thingModelUrl the URL from which the ThingModel was fetched. - * @param dittoHeaders the DittoHeaders for possibly thrown DittoRuntimeException which might occur during the - * generation. - * @return the generated ThingDescription for the given {@code thingId} based on the passed in {@code thingModel}. - * @throws org.eclipse.ditto.wot.model.WotThingModelInvalidException if the WoT ThingModel did not contain the - * mandatory {@code "@type"} being {@code "tm:ThingModel"} - */ - CompletionStage generateThingDescription(ThingId thingId, - @Nullable Thing thing, - @Nullable JsonObject placeholderLookupObject, - @Nullable String featureId, - ThingModel thingModel, - URL thingModelUrl, - DittoHeaders dittoHeaders); - - /** - * Creates a new instance of WotThingDescriptionGenerator with the given {@code wotConfig}. - * - * @param actorSystem the actor system to use. - * @param wotConfig the WoTConfig to use for creating the generator. - * @param thingModelFetcher the ThingModel fetcher to fetch linked other ThingModels during the TD generation - * process. - * @return the created WotThingDescriptionGenerator. - */ - static WotThingDescriptionGenerator of(final ActorSystem actorSystem, - final WotConfig wotConfig, - final WotThingModelFetcher thingModelFetcher) { - return new DefaultWotThingDescriptionGenerator(actorSystem, wotConfig, thingModelFetcher); - } -} diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/package-info.java b/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/package-info.java new file mode 100644 index 00000000000..c7e6892b611 --- /dev/null +++ b/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +/** + * The Ditto WoT integration using the "Ditto WoT API" with Akka specifics, e.g. in order to fetch WoT models via HTTP. + * @since 3.6.0 + */ +@org.eclipse.ditto.utils.jsr305.annotations.AllParametersAndReturnValuesAreNonnullByDefault +package org.eclipse.ditto.wot.integration; \ No newline at end of file diff --git a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/DefaultWotThingDescriptionProvider.java b/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/DefaultWotThingDescriptionProvider.java deleted file mode 100644 index 4e51f11e45d..00000000000 --- a/wot/integration/src/main/java/org/eclipse/ditto/wot/integration/provider/DefaultWotThingDescriptionProvider.java +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright (c) 2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipse.ditto.wot.integration.provider; - -import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull; - -import java.net.URL; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.Executor; - -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; - -import org.apache.pekko.actor.AbstractExtensionId; -import org.apache.pekko.actor.ActorSystem; -import org.apache.pekko.actor.ExtendedActorSystem; -import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException; -import org.eclipse.ditto.base.model.headers.DittoHeaders; -import org.eclipse.ditto.base.model.signals.FeatureToggle; -import org.eclipse.ditto.internal.utils.config.DefaultScopedConfig; -import org.eclipse.ditto.internal.utils.pekko.logging.DittoLoggerFactory; -import org.eclipse.ditto.internal.utils.pekko.logging.ThreadSafeDittoLogger; -import org.eclipse.ditto.json.JsonValue; -import org.eclipse.ditto.things.model.DefinitionIdentifier; -import org.eclipse.ditto.things.model.Feature; -import org.eclipse.ditto.things.model.FeatureDefinition; -import org.eclipse.ditto.things.model.Thing; -import org.eclipse.ditto.things.model.ThingDefinition; -import org.eclipse.ditto.things.model.ThingId; -import org.eclipse.ditto.wot.integration.config.DefaultWotConfig; -import org.eclipse.ditto.wot.integration.config.WotConfig; -import org.eclipse.ditto.wot.integration.generator.WotThingDescriptionGenerator; -import org.eclipse.ditto.wot.integration.generator.WotThingSkeletonGenerator; -import org.eclipse.ditto.wot.model.ThingDefinitionInvalidException; -import org.eclipse.ditto.wot.model.ThingDescription; -import org.eclipse.ditto.wot.model.WotInternalErrorException; - -/** - * Default Ditto specific implementation of {@link WotThingDescriptionProvider}. - */ -@Immutable -final class DefaultWotThingDescriptionProvider implements WotThingDescriptionProvider { - - private static final ThreadSafeDittoLogger LOGGER = - DittoLoggerFactory.getThreadSafeLogger(DefaultWotThingDescriptionProvider.class); - - public static final String MODEL_PLACEHOLDERS_KEY = "model-placeholders"; - - private final WotConfig wotConfig; - private final WotThingModelFetcher thingModelFetcher; - private final WotThingDescriptionGenerator thingDescriptionGenerator; - private final WotThingSkeletonGenerator thingSkeletonGenerator; - private final Executor executor; - - private DefaultWotThingDescriptionProvider(final ActorSystem actorSystem, final WotConfig wotConfig) { - this.wotConfig = checkNotNull(wotConfig, "wotConfig"); - thingModelFetcher = new DefaultWotThingModelFetcher(actorSystem, wotConfig); - thingDescriptionGenerator = WotThingDescriptionGenerator.of(actorSystem, wotConfig, thingModelFetcher); - thingSkeletonGenerator = WotThingSkeletonGenerator.of(actorSystem, thingModelFetcher); - executor = actorSystem.dispatchers().lookup("wot-dispatcher"); - } - - /** - * Returns a new {@code DefaultWotThingDescriptionProvider} for the given parameters. - * - * @param actorSystem the actor system to use. - * @param wotConfig the WoT config to use. - * @return the DefaultWotThingDescriptionProvider. - * @throws NullPointerException if any argument is {@code null}. - */ - public static DefaultWotThingDescriptionProvider of(final ActorSystem actorSystem, final WotConfig wotConfig) { - return new DefaultWotThingDescriptionProvider(actorSystem, wotConfig); - } - - @Override - public CompletionStage provideThingTD(@Nullable final ThingDefinition thingDefinition, - final ThingId thingId, - @Nullable final Thing thing, - final DittoHeaders dittoHeaders) { - if (null != thingDefinition) { - return getWotThingDescriptionForThing(thingDefinition, thingId, thing, dittoHeaders); - } else { - throw ThingDefinitionInvalidException.newBuilder(null) - .dittoHeaders(dittoHeaders) - .build(); - } - } - - @Override - public CompletionStage provideFeatureTD(final ThingId thingId, - @Nullable final Thing thing, - final Feature feature, - final DittoHeaders dittoHeaders) { - - checkNotNull(feature, "feature"); - if (feature.getDefinition().isPresent()) { - return getWotThingDescriptionForFeature(thingId, thing, feature, dittoHeaders); - } else { - throw ThingDefinitionInvalidException.newBuilder(null) - .dittoHeaders(dittoHeaders) - .build(); - } - } - - @Override - public CompletionStage> provideThingSkeletonForCreation(final ThingId thingId, - @Nullable final ThingDefinition thingDefinition, - final DittoHeaders dittoHeaders) { - - final ThreadSafeDittoLogger logger = LOGGER.withCorrelationId(dittoHeaders); - if (FeatureToggle.isWotIntegrationFeatureEnabled() && - wotConfig.getCreationConfig().getThingCreationConfig().isSkeletonCreationEnabled() && - null != thingDefinition) { - final Optional urlOpt = thingDefinition.getUrl(); - if (urlOpt.isPresent()) { - final URL url = urlOpt.get(); - logger.debug("Fetching ThingModel from <{}> in order to create Thing skeleton for new Thing " + - "with id <{}>", url, thingId); - return thingModelFetcher.fetchThingModel(url, dittoHeaders) - .thenComposeAsync(thingModel -> thingSkeletonGenerator.generateThingSkeleton( - thingId, - thingModel, - url, - wotConfig.getCreationConfig() - .getThingCreationConfig() - .shouldGenerateDefaultsForOptionalProperties(), - dittoHeaders - ), - executor - ) - .handle((thingSkeleton, throwable) -> { - if (throwable != null) { - logger.info("Could not fetch ThingModel or generate Thing skeleton based on it due " + - "to: <{}: {}>", - throwable.getClass().getSimpleName(), throwable.getMessage(), throwable); - if (wotConfig.getCreationConfig() - .getThingCreationConfig() - .shouldThrowExceptionOnWotErrors()) { - throw DittoRuntimeException.asDittoRuntimeException( - throwable, t -> WotInternalErrorException.newBuilder() - .dittoHeaders(dittoHeaders) - .cause(t) - .build() - ); - } else { - return Optional.empty(); - } - } else { - logger.debug("Created Thing skeleton for new Thing with id <{}>: <{}>", thingId, - thingSkeleton); - return thingSkeleton; - } - }); - } else { - return CompletableFuture.completedFuture(Optional.empty()); - } - } else { - return CompletableFuture.completedFuture(Optional.empty()); - } - } - - @Override - public CompletionStage> provideFeatureSkeletonForCreation(final String featureId, - @Nullable final FeatureDefinition featureDefinition, final DittoHeaders dittoHeaders) { - - final ThreadSafeDittoLogger logger = LOGGER.withCorrelationId(dittoHeaders); - if (FeatureToggle.isWotIntegrationFeatureEnabled() && - wotConfig.getCreationConfig().getFeatureCreationConfig().isSkeletonCreationEnabled() && - null != featureDefinition) { - final Optional urlOpt = featureDefinition.getFirstIdentifier().getUrl(); - if (urlOpt.isPresent()) { - final URL url = urlOpt.get(); - logger.debug("Fetching ThingModel from <{}> in order to create Feature skeleton for new Feature " + - "with id <{}>", url, featureId); - return thingModelFetcher.fetchThingModel(url, dittoHeaders) - .thenComposeAsync(thingModel -> thingSkeletonGenerator.generateFeatureSkeleton( - featureId, - thingModel, - url, - wotConfig.getCreationConfig() - .getFeatureCreationConfig() - .shouldGenerateDefaultsForOptionalProperties(), - dittoHeaders - ), - executor - ) - .handle((featureSkeleton, throwable) -> { - if (throwable != null) { - logger.info("Could not fetch ThingModel or generate Feature skeleton based on it due " + - "to: <{}: {}>", - throwable.getClass().getSimpleName(), throwable.getMessage(), throwable); - if (wotConfig.getCreationConfig() - .getFeatureCreationConfig() - .shouldThrowExceptionOnWotErrors()) { - throw DittoRuntimeException.asDittoRuntimeException( - throwable, t -> WotInternalErrorException.newBuilder() - .dittoHeaders(dittoHeaders) - .cause(t) - .build() - ); - } else { - return Optional.empty(); - } - } else { - logger.debug("Created Feature skeleton for new Feature with id <{}>: <{}>", featureId, - featureSkeleton); - return featureSkeleton; - } - }); - } else { - return CompletableFuture.completedFuture(Optional.empty()); - } - } else { - return CompletableFuture.completedFuture(Optional.empty()); - } - } - - /** - * Download TM, add it to local cache + build TD + return it - */ - private CompletionStage getWotThingDescriptionForThing(final ThingDefinition definitionIdentifier, - final ThingId thingId, - @Nullable final Thing thing, - final DittoHeaders dittoHeaders) { - - final Optional urlOpt = definitionIdentifier.getUrl(); - if (urlOpt.isPresent()) { - final URL url = urlOpt.get(); - return thingModelFetcher.fetchThingModel(url, dittoHeaders) - .thenComposeAsync(thingModel -> thingDescriptionGenerator - .generateThingDescription(thingId, - thing, - Optional.ofNullable(thing) - .flatMap(Thing::getAttributes) - .flatMap(a -> a.getValue(MODEL_PLACEHOLDERS_KEY)) - .filter(JsonValue::isObject) - .map(JsonValue::asObject) - .orElse(null), - null, - thingModel, - url, - dittoHeaders - ), - executor - ) - .exceptionally(throwable -> { - throw DittoRuntimeException.asDittoRuntimeException(throwable, t -> - WotInternalErrorException.newBuilder() - .dittoHeaders(dittoHeaders) - .cause(t) - .build()); - }); - } else { - throw ThingDefinitionInvalidException.newBuilder(definitionIdentifier) - .dittoHeaders(dittoHeaders) - .build(); - } - } - - /** - * Download TM, add it to local cache + build TD + return it - */ - private CompletionStage getWotThingDescriptionForFeature(final ThingId thingId, - @Nullable final Thing thing, - final Feature feature, - final DittoHeaders dittoHeaders) { - - final Optional definitionIdentifier = feature.getDefinition() - .map(FeatureDefinition::getFirstIdentifier); - final Optional urlOpt = definitionIdentifier.flatMap(DefinitionIdentifier::getUrl); - if (urlOpt.isPresent()) { - final URL url = urlOpt.get(); - return thingModelFetcher.fetchThingModel(url, dittoHeaders) - .thenComposeAsync(thingModel -> thingDescriptionGenerator - .generateThingDescription(thingId, - thing, - feature.getProperties() - .flatMap(p -> p.getValue(MODEL_PLACEHOLDERS_KEY)) - .filter(JsonValue::isObject) - .map(JsonValue::asObject) - .orElse(null), - feature.getId(), - thingModel, - url, - dittoHeaders - ), - executor - ) - .exceptionally(throwable -> { - throw DittoRuntimeException.asDittoRuntimeException(throwable, t -> - WotInternalErrorException.newBuilder() - .dittoHeaders(dittoHeaders) - .cause(t) - .build()); - }); - } else { - throw ThingDefinitionInvalidException.newBuilder(definitionIdentifier.orElse(null)) - .dittoHeaders(dittoHeaders) - .build(); - } - } - - static final class ExtensionId extends AbstractExtensionId { - - private static final String WOT_PARENT_CONFIG_PATH = "things"; - - static final ExtensionId INSTANCE = new ExtensionId(); - - private ExtensionId() {} - - @Override - public WotThingDescriptionProvider createExtension(final ExtendedActorSystem system) { - final WotConfig wotConfig = DefaultWotConfig.of( - DefaultScopedConfig.dittoScoped(system.settings().config()).getConfig(WOT_PARENT_CONFIG_PATH) - ); - return of(system, wotConfig); - } - } - -} diff --git a/wot/model/pom.xml b/wot/model/pom.xml index 9ea16a36279..6c3f4fe0ffd 100755 --- a/wot/model/pom.xml +++ b/wot/model/pom.xml @@ -91,6 +91,7 @@ + org.atteo.classindex, !org.eclipse.ditto.utils.jsr305.annotations, org.eclipse.ditto.* diff --git a/wot/pom.xml b/wot/pom.xml index 4847a056177..0cf9e0bca19 100644 --- a/wot/pom.xml +++ b/wot/pom.xml @@ -1,6 +1,6 @@ + + 4.0.0 + + + org.eclipse.ditto + ditto-wot + ${revision} + + + ditto-wot-validation + Eclipse Ditto :: WoT :: Validation + + + + + + + org.eclipse.ditto + ditto-json + + + org.eclipse.ditto + ditto-base-model + + + org.eclipse.ditto + ditto-things-model + + + + org.eclipse.ditto + ditto-wot-model + + + + org.eclipse.ditto + ditto-internal-utils-config + + + + com.networknt + json-schema-validator + + + + ch.qos.logback + logback-classic + test + + + + \ No newline at end of file diff --git a/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/DefaultWotThingModelValidation.java b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/DefaultWotThingModelValidation.java new file mode 100644 index 00000000000..5ad3e93c5f4 --- /dev/null +++ b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/DefaultWotThingModelValidation.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.wot.validation; + +import java.util.AbstractMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.eclipse.ditto.base.model.headers.DittoHeaders; +import org.eclipse.ditto.json.JsonKey; +import org.eclipse.ditto.json.JsonObject; +import org.eclipse.ditto.json.JsonPointer; +import org.eclipse.ditto.json.JsonValue; +import org.eclipse.ditto.things.model.Attributes; +import org.eclipse.ditto.things.model.Feature; +import org.eclipse.ditto.things.model.Thing; +import org.eclipse.ditto.wot.model.Properties; +import org.eclipse.ditto.wot.model.ThingModel; +import org.eclipse.ditto.wot.model.ThingSkeleton; +import org.eclipse.ditto.wot.model.TmOptionalElement; +import org.eclipse.ditto.wot.validation.config.TmValidationConfig; + +import com.networknt.schema.InputFormat; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.OutputFormat; +import com.networknt.schema.output.OutputUnit; + +final class DefaultWotThingModelValidation implements WotThingModelValidation { + + private static final String ATTRIBUTES = "attributes"; + + private final TmValidationConfig validationConfig; + + public DefaultWotThingModelValidation(final TmValidationConfig validationConfig) { + + this.validationConfig = validationConfig; + } + + @Override + + public CompletionStage validateThing(final ThingSkeleton thingSkeleton, + final Thing thing, + final DittoHeaders dittoHeaders) { + + if (validationConfig.getThingValidationConfig().isEnforceAttributes()) { + + return thingSkeleton.getProperties() + .map(tdProperties -> { + final Attributes attributes = + thing.getAttributes().orElseGet(() -> Attributes.newBuilder().build()); + + final CompletableFuture ensureRequiredPropertiesStage = + ensureRequiredProperties(thingSkeleton, dittoHeaders, tdProperties, attributes, + ATTRIBUTES, JsonPointer.of(ATTRIBUTES)); + final CompletableFuture ensureOnlyDefinedPropertiesStage = + ensureOnlyDefinedProperties(dittoHeaders, tdProperties, attributes, ATTRIBUTES); + final CompletableFuture validatePropertiesStage = + getValidatePropertiesStage(dittoHeaders, tdProperties, attributes, + ATTRIBUTES, JsonPointer.of(ATTRIBUTES)); + + return CompletableFuture.allOf( + ensureRequiredPropertiesStage, + ensureOnlyDefinedPropertiesStage, + validatePropertiesStage + ); + }).orElseGet(DefaultWotThingModelValidation::success); + } else { + return success(); + } + } + + private CompletableFuture ensureRequiredProperties(final ThingSkeleton thingSkeleton, + final DittoHeaders dittoHeaders, final Properties tdProperties, final JsonObject propertiesContainer, + final String containerName, final JsonPointer pointerPrefix) { + + final CompletableFuture requiredPropertiesStage; + if (thingSkeleton instanceof ThingModel thingModel) { + final Set requiredProperties = extractRequiredProperties(tdProperties, thingModel); + + propertiesContainer.getKeys().stream().map(JsonKey::toString).forEach(requiredProperties::remove); + if (!requiredProperties.isEmpty()) { + final var exceptionBuilder = WotThingModelPayloadValidationException + .newBuilder("Required properties were missing from the Thing's " + containerName); + requiredProperties.forEach(rp -> + exceptionBuilder.addValidationDetail( + pointerPrefix.addLeaf(JsonKey.of(rp)), + List.of(containerName + " <" + rp + "> is non optional and must be present") + ) + ); + requiredPropertiesStage = CompletableFuture + .failedFuture(exceptionBuilder.dittoHeaders(dittoHeaders).build()); + } else { + requiredPropertiesStage = success(); + } + } else { + requiredPropertiesStage = success(); + } + return requiredPropertiesStage; + } + + private CompletableFuture ensureOnlyDefinedProperties(final DittoHeaders dittoHeaders, + final Properties tdProperties, final JsonObject propertiesContainer, final String containerName) { + final CompletableFuture ensureOnlyDefinedPropertiesStage; + if (!validationConfig.getThingValidationConfig().isAllowNonModeledAttributes()) { + final Set allDefinedPropertyKeys = tdProperties.keySet(); + final Set allAvailablePropertiesKeys = + propertiesContainer.getKeys().stream().map(JsonKey::toString) + .collect(Collectors.toCollection(LinkedHashSet::new)); + allAvailablePropertiesKeys.removeAll(allDefinedPropertyKeys); + if (!allAvailablePropertiesKeys.isEmpty()) { + final var exceptionBuilder = WotThingModelPayloadValidationException + .newBuilder("The Thing's " + containerName + " contained " + containerName + + " keys which were not defined in the model: " + allAvailablePropertiesKeys); + ensureOnlyDefinedPropertiesStage = CompletableFuture.failedFuture(exceptionBuilder + .dittoHeaders(dittoHeaders) + .build()); + } else { + ensureOnlyDefinedPropertiesStage = success(); + } + } else { + ensureOnlyDefinedPropertiesStage = success(); + } + return ensureOnlyDefinedPropertiesStage; + } + + private static CompletableFuture getValidatePropertiesStage(final DittoHeaders dittoHeaders, + final Properties tdProperties, final JsonObject propertiesContainer, final String containerName, + final JsonPointer pointerPrefix) { + + final CompletableFuture validatePropertiesStage; + final Map invalidProperties = + determineInvalidProperties(tdProperties, propertiesContainer::getValue); + if (!invalidProperties.isEmpty()) { + final var exceptionBuilder = WotThingModelPayloadValidationException + .newBuilder("The Thing's " + containerName + " contained validation errors, " + + "check the validation details."); + invalidProperties.forEach((key, value) -> exceptionBuilder.addValidationDetail( + pointerPrefix.addLeaf(JsonKey.of(key)), + value.getDetails().stream() + .map(ou -> ou.getInstanceLocation() + ": " + ou.getErrors()) + .toList() + )); + validatePropertiesStage = CompletableFuture.failedFuture(exceptionBuilder + .dittoHeaders(dittoHeaders) + .build()); + } else { + validatePropertiesStage = success(); + } + return validatePropertiesStage; + } + + private static Map determineInvalidProperties(final Properties tdProperties, + final Function> propertyExtractor) { + + return tdProperties.entrySet().stream() + .flatMap(tdPropertyEntry -> + propertyExtractor.apply(tdPropertyEntry.getKey()) + .map(attributeValue -> { + final JsonSchema jsonSchema = JsonSchemaExtractor + .extractFromSingleDataSchema(tdPropertyEntry.getValue()); + final String jsonString = attributeValue.toString(); + // TODO TJ surely can be done more efficiently + return new AbstractMap.SimpleEntry<>( + tdPropertyEntry.getKey(), + jsonSchema.validate(jsonString, InputFormat.JSON, OutputFormat.LIST) + ); + }) + .filter(entry -> !entry.getValue().isValid()) + .stream() + ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + @Override + public CompletionStage validateFeature(final ThingSkeleton thingSkeleton, + final Feature feature, + final DittoHeaders dittoHeaders) { + // TODO TJ implement + return success(); + } + + private static CompletableFuture success() { + return CompletableFuture.completedFuture(null); + } + + private Set extractRequiredProperties(final Properties tdProperties, final ThingModel thingModel) { + return thingModel.getTmOptional().map(tmOptionalElements -> { + final Set allDefinedProperties = tdProperties.keySet(); + final Set allOptionalProperties = tmOptionalElements.stream() + .map(TmOptionalElement::toString) + .filter(el -> el.startsWith("/properties/")) + .map(el -> el.replace("/properties/", "")) + .collect(Collectors.toSet()); + final Set allRequiredProperties = new HashSet<>(allDefinedProperties); + allRequiredProperties.removeAll(allOptionalProperties); + return allRequiredProperties; + }).orElseGet(HashSet::new); + } +} diff --git a/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/JsonSchemaExtractor.java b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/JsonSchemaExtractor.java new file mode 100644 index 00000000000..f0c39a81946 --- /dev/null +++ b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/JsonSchemaExtractor.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.wot.validation; + +import org.eclipse.ditto.wot.model.SingleDataSchema; + +import com.networknt.schema.JsonMetaSchema; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.NonValidationKeyword; +import com.networknt.schema.SchemaId; +import com.networknt.schema.SchemaValidatorsConfig; +import com.networknt.schema.SpecVersion; + +/** + * TODO TJ doc + * + * @since 3.6.0 + */ +public final class JsonSchemaExtractor { + + public static JsonSchema extractFromSingleDataSchema(final SingleDataSchema dataSchema) { + final String jsonString = dataSchema.toJsonString(); + // TODO TJ that can surely be done more efficiently + final JsonMetaSchema.Builder metaSchemaBuilder = JsonMetaSchema.builder(SchemaId.V7, JsonMetaSchema.getV7()); + metaSchemaBuilder.addKeyword(new NonValidationKeyword("@type")); + metaSchemaBuilder.addKeyword(new NonValidationKeyword("unit")); + return JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7, builder -> + builder.addMetaSchema(metaSchemaBuilder.build())) + .getSchema(jsonString, new SchemaValidatorsConfig()); + } +} diff --git a/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/WotThingModelPayloadValidationException.java b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/WotThingModelPayloadValidationException.java new file mode 100644 index 00000000000..ac8d1610c20 --- /dev/null +++ b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/WotThingModelPayloadValidationException.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.wot.validation; + +import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull; + +import java.io.Serial; +import java.net.URI; +import java.util.AbstractMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import javax.annotation.concurrent.NotThreadSafe; + +import org.eclipse.ditto.base.model.common.HttpStatus; +import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException; +import org.eclipse.ditto.base.model.exceptions.DittoRuntimeExceptionBuilder; +import org.eclipse.ditto.base.model.headers.DittoHeaders; +import org.eclipse.ditto.base.model.json.FieldType; +import org.eclipse.ditto.base.model.json.JsonParsableException; +import org.eclipse.ditto.base.model.json.JsonSchemaVersion; +import org.eclipse.ditto.json.JsonCollectors; +import org.eclipse.ditto.json.JsonFactory; +import org.eclipse.ditto.json.JsonField; +import org.eclipse.ditto.json.JsonFieldDefinition; +import org.eclipse.ditto.json.JsonObject; +import org.eclipse.ditto.json.JsonObjectBuilder; +import org.eclipse.ditto.json.JsonPointer; +import org.eclipse.ditto.json.JsonValue; +import org.eclipse.ditto.wot.model.WotException; + +/** + * Exception thrown when a Ditto Thing (or parts of it), so the payload, could not be validated against the WoT Model. + * + * @since 3.6.0 + */ +@Immutable +@JsonParsableException(errorCode = WotThingModelPayloadValidationException.ERROR_CODE) +public final class WotThingModelPayloadValidationException extends DittoRuntimeException implements WotException { + + /** + * Error code of this exception. + */ + public static final String ERROR_CODE = ERROR_CODE_PREFIX + "payload.validation.error"; + + private static final String DEFAULT_MESSAGE = + "The provided payload did not conform to the specified WoT (Web of Things) model."; + + @Serial + private static final long serialVersionUID = -236554134452227841L; + + private final Map> validationDetails; + + private WotThingModelPayloadValidationException(final DittoHeaders dittoHeaders, + @Nullable final String message, + @Nullable final String description, + @Nullable final Throwable cause, + @Nullable final URI href, + final Map> validationDetails) { + super(ERROR_CODE, HttpStatus.BAD_REQUEST, dittoHeaders, message, description, cause, href); + this.validationDetails = validationDetails; + } + + /** + * A mutable builder for a {@code WotThingModelPayloadValidationException}. + * + * @param validationDescription the details about what was not valid. + * @return the builder. + * @throws NullPointerException if {@code validationDescription} is {@code null}. + */ + public static Builder newBuilder(final String validationDescription) { + return new Builder(validationDescription); + } + + /** + * Constructs a new {@code WotThingModelPayloadValidationException} object with the exception message extracted from the given + * JSON object. + * + * @param jsonObject the JSON to read the {@link org.eclipse.ditto.base.model.exceptions.DittoRuntimeException.JsonFields#MESSAGE} field from. + * @param dittoHeaders the headers of the command which resulted in this exception. + * @return the new WotThingModelPayloadValidationException. + * @throws NullPointerException if any argument is {@code null}. + * @throws org.eclipse.ditto.json.JsonMissingFieldException if this JsonObject did not contain an error message. + * @throws org.eclipse.ditto.json.JsonParseException if the passed in {@code jsonObject} was not in the expected + * format. + */ + public static WotThingModelPayloadValidationException fromJson(final JsonObject jsonObject, + final DittoHeaders dittoHeaders) { + + return DittoRuntimeException.fromJson(jsonObject, dittoHeaders, + new Builder(readValidationDetails(jsonObject)) + ); + } + + @Override + public DittoRuntimeException setDittoHeaders(final DittoHeaders dittoHeaders) { + return new Builder(validationDetails) + .message(getMessage()) + .description(getDescription().orElse(null)) + .cause(getCause()) + .href(getHref().orElse(null)) + .dittoHeaders(dittoHeaders) + .build(); + } + + private static Map> readValidationDetails(final JsonObject jsonObject) { + checkNotNull(jsonObject, "JSON object"); + return jsonObject.getValue(JsonFields.VALIDATION_DETAILS) + .map(validationDetails -> validationDetails.stream() + .map(field -> new AbstractMap.SimpleEntry<>( + JsonPointer.of(field.getKey().toString()), + field.getValue().asArray().stream().map(JsonValue::formatAsString).toList()) + ) + ).stream() + .flatMap(Function.identity()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (u, v) -> { + throw new IllegalStateException(String.format("Duplicate key %s", u)); + }, LinkedHashMap::new)); + } + + @Override + protected void appendToJson(final JsonObjectBuilder jsonObjectBuilder, final Predicate predicate) { + final JsonObject detailsObject = validationDetails.entrySet().stream() + .map(entry -> JsonField.newInstance(entry.getKey().toString(), + entry.getValue().stream() + .map(JsonValue::of) + .collect(JsonCollectors.valuesToArray()) + )) + .collect(JsonCollectors.fieldsToObject()); + if (!detailsObject.isEmpty()) { + jsonObjectBuilder.set(JsonFields.VALIDATION_DETAILS, detailsObject, predicate); + } + } + + @Override + public boolean equals(@Nullable final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + final WotThingModelPayloadValidationException that = (WotThingModelPayloadValidationException) o; + return Objects.equals(validationDetails, that.validationDetails); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), validationDetails); + } + + @Override + public String toString() { + return getClass().getSimpleName() + " [" + + "message='" + getMessage() + '\'' + + ", errorCode=" + getErrorCode() + + ", httpStatus=" + getHttpStatus() + + ", description='" + getDescription().orElse(null) + '\'' + + ", href=" + getHref().orElse(null) + + ", validationDetails=" + validationDetails + + ", dittoHeaders=" + getDittoHeaders() + + ']'; + } + + /** + * A mutable builder with a fluent API for a {@link WotThingModelPayloadValidationException}. + */ + @NotThreadSafe + public static final class Builder extends DittoRuntimeExceptionBuilder { + + private Map> validationDetails; + + private Builder() { + validationDetails = new LinkedHashMap<>(); + message(DEFAULT_MESSAGE); + } + + private Builder(final String validationDescription) { + this(); + description(checkNotNull(validationDescription, "validationDescription")); + } + + private Builder(final Map> validationDetails) { + this(); + this.validationDetails = validationDetails; + } + + public Builder addValidationDetail(final JsonPointer jsonPointer, final List validationErrors) { + validationDetails.put(jsonPointer, validationErrors); + return this; + } + + @Override + protected WotThingModelPayloadValidationException doBuild(final DittoHeaders dittoHeaders, + @Nullable final String message, + @Nullable final String description, + @Nullable final Throwable cause, + @Nullable final URI href) { + return new WotThingModelPayloadValidationException(dittoHeaders, message, description, cause, href, + validationDetails); + } + + } + + /** + * An enumeration of the known {@link org.eclipse.ditto.json.JsonField}s of a {@code WotThingModelPayloadValidationException}. + */ + @Immutable + public static final class JsonFields { + + /** + * JSON field containing the validation details. + */ + static final JsonFieldDefinition VALIDATION_DETAILS = + JsonFactory.newJsonObjectFieldDefinition("validationDetails", FieldType.REGULAR, + JsonSchemaVersion.V_2); + + private JsonFields() { + throw new AssertionError(); + } + + } +} diff --git a/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/WotThingModelValidation.java b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/WotThingModelValidation.java new file mode 100644 index 00000000000..efac7226a7a --- /dev/null +++ b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/WotThingModelValidation.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.wot.validation; + +import java.util.concurrent.CompletionStage; + +import org.eclipse.ditto.base.model.headers.DittoHeaders; +import org.eclipse.ditto.things.model.Feature; +import org.eclipse.ditto.things.model.Thing; +import org.eclipse.ditto.wot.model.ThingSkeleton; +import org.eclipse.ditto.wot.validation.config.TmValidationConfig; + +/** + * @since 3.6.0 + */ +public interface WotThingModelValidation { + + /** + * TODO TJ doc + */ + CompletionStage validateThing(ThingSkeleton thingSkeleton, + Thing thing, + DittoHeaders dittoHeaders); + + /** + * TODO TJ doc + */ + CompletionStage validateFeature(ThingSkeleton thingSkeleton, + Feature feature, + DittoHeaders dittoHeaders); + + /** + * TODO TJ doc + * @return + */ + static WotThingModelValidation createInstance(final TmValidationConfig validationConfig) { + return new DefaultWotThingModelValidation(validationConfig); + } +} diff --git a/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/FeatureValidationConfig.java b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/FeatureValidationConfig.java new file mode 100644 index 00000000000..d1a3f34a1d8 --- /dev/null +++ b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/FeatureValidationConfig.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.wot.validation.config; + +import javax.annotation.concurrent.Immutable; + +import org.eclipse.ditto.internal.utils.config.KnownConfigValue; + +/** + * Provides configuration settings for WoT (Web of Things) based validation of Features. + * + * @since 3.6.0 + */ +@Immutable +public interface FeatureValidationConfig { + + /** + * @return whether to enforce/validate properties of a feature following the defined WoT properties. + */ + boolean isEnforceProperties(); + + /** + * @return whether to allow/accept persisting properties which are not defined as properties in the WoT model. + */ + boolean isAllowNonModeledProperties(); + + /** + * @return whether to enforce/validate desired properties of a feature following the defined WoT properties. + */ + boolean isEnforceDesiredProperties(); + + /** + * @return whether to allow/accept persisting desired properties which are not defined as properties in the WoT model. + */ + boolean isAllowNonModeledDesiredProperties(); + + /** + * @return whether to enforce/validate inbox messages to a feature following the defined WoT actions. + */ + boolean isEnforceInboxMessages(); + + /** + * @return whether to allow/accept dispatching of inbox messages which are not defined as actions in the WoT model. + */ + boolean isAllowNonModeledInboxMessages(); + + /** + * @return whether to enforce/validate outbox messages from a feature following the defined WoT actions. + */ + boolean isEnforceOutboxMessages(); + + /** + * @return whether to allow/accept dispatching of outbox messages which are not defined as actions in the WoT model. + */ + boolean isAllowNonModeledOutboxMessages(); + + + /** + * An enumeration of the known config path expressions and their associated default values for + * {@code FeatureValidationConfig}. + */ + enum ConfigValue implements KnownConfigValue { + + ENFORCE_PROPERTIES("enforce-properties", true), + + ALLOW_NON_MODELED_PROPERTIES("allow-non-modeled-properties", false), + + ENFORCE_DESIRED_PROPERTIES("enforce-desired-properties", true), + + ALLOW_NON_MODELED_DESIRED_PROPERTIES("allow-non-modeled-desired-properties", false), + + ENFORCE_INBOX_MESSAGES("enforce-inbox-messages", true), + + ALLOW_NON_MODELED_INBOX_MESSAGES("allow-non-modeled-inbox-messages", false), + + ENFORCE_OUTBOX_MESSAGES("enforce-outbox-messages", true), + + ALLOW_NON_MODELED_OUTBOX_MESSAGES("allow-non-modeled-outbox-messages", false); + + + private final String path; + private final Object defaultValue; + + ConfigValue(final String thePath, final Object theDefaultValue) { + path = thePath; + defaultValue = theDefaultValue; + } + + @Override + public Object getDefaultValue() { + return defaultValue; + } + + @Override + public String getConfigPath() { + return path; + } + + } +} diff --git a/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/ThingValidationConfig.java b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/ThingValidationConfig.java new file mode 100644 index 00000000000..ece0424636f --- /dev/null +++ b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/ThingValidationConfig.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.wot.validation.config; + +import javax.annotation.concurrent.Immutable; + +import org.eclipse.ditto.internal.utils.config.KnownConfigValue; + +/** + * Provides configuration settings for WoT (Web of Things) based validation of Things. + * + * @since 3.6.0 + */ +@Immutable +public interface ThingValidationConfig { + + /** + * @return whether to enforce/validate attributes of a thing following the defined WoT properties. + */ + boolean isEnforceAttributes(); + + /** + * @return whether to allow/accept persisting attributes which are not defined as properties in the WoT model. + */ + boolean isAllowNonModeledAttributes(); + + /** + * @return whether to enforce/validate inbox messages to a thing following the defined WoT actions. + */ + boolean isEnforceInboxMessages(); + + /** + * @return whether to allow/accept dispatching of inbox messages which are not defined as actions in the WoT model. + */ + boolean isAllowNonModeledInboxMessages(); + + /** + * @return whether to enforce/validate outbox messages from a thing following the defined WoT actions. + */ + boolean isEnforceOutboxMessages(); + + /** + * @return whether to allow/accept dispatching of outbox messages which are not defined as actions in the WoT model. + */ + boolean isAllowNonModeledOutboxMessages(); + + + /** + * An enumeration of the known config path expressions and their associated default values for + * {@code ThingValidationConfig}. + */ + enum ConfigValue implements KnownConfigValue { + + ENFORCE_ATTRIBUTES("enforce-attributes", true), + + ALLOW_NON_MODELED_ATTRIBUTES("allow-non-modeled-attributes", false), + + ENFORCE_INBOX_MESSAGES("enforce-inbox-messages", true), + + ALLOW_NON_MODELED_INBOX_MESSAGES("allow-non-modeled-inbox-messages", false), + + ENFORCE_OUTBOX_MESSAGES("enforce-outbox-messages", true), + + ALLOW_NON_MODELED_OUTBOX_MESSAGES("allow-non-modeled-outbox-messages", false); + + + private final String path; + private final Object defaultValue; + + ConfigValue(final String thePath, final Object theDefaultValue) { + path = thePath; + defaultValue = theDefaultValue; + } + + @Override + public Object getDefaultValue() { + return defaultValue; + } + + @Override + public String getConfigPath() { + return path; + } + + } +} diff --git a/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/TmValidationConfig.java b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/TmValidationConfig.java new file mode 100644 index 00000000000..e7afc69530c --- /dev/null +++ b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/config/TmValidationConfig.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.wot.validation.config; + +import javax.annotation.concurrent.Immutable; + +import org.eclipse.ditto.internal.utils.config.KnownConfigValue; + +/** + * Provides configuration settings for WoT (Web of Things) integration regarding the validation of Things and Features + * based on their WoT ThingModels. + * + * @since 3.6.0 + */ +@Immutable +public interface TmValidationConfig { + + /** + * @return whether the ThingModel validation of Things/Features should be enabled or not. + */ + boolean isEnabled(); + + /** + * @return the config for validating things. + */ + ThingValidationConfig getThingValidationConfig(); + + /** + * @return the config for validating features. + */ + FeatureValidationConfig getFeatureValidationConfig(); + + + /** + * An enumeration of the known config path expressions and their associated default values for + * {@code TmValidationConfig}. + */ + enum ConfigValue implements KnownConfigValue { + + /** + * Whether the TM based validation should be enabled or not. + */ + ENABLED("enabled", true); + + + private final String path; + private final Object defaultValue; + + ConfigValue(final String thePath, final Object theDefaultValue) { + path = thePath; + defaultValue = theDefaultValue; + } + + @Override + public Object getDefaultValue() { + return defaultValue; + } + + @Override + public String getConfigPath() { + return path; + } + + } +} diff --git a/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/package-info.java b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/package-info.java new file mode 100644 index 00000000000..e472596f256 --- /dev/null +++ b/wot/validation/src/main/java/org/eclipse/ditto/wot/validation/package-info.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +/** + * @since 3.6.0 + */ +@org.eclipse.ditto.utils.jsr305.annotations.AllParametersAndReturnValuesAreNonnullByDefault +package org.eclipse.ditto.wot.validation; \ No newline at end of file