From d97fb58dfe73b95264f443d403e29151337b08cf Mon Sep 17 00:00:00 2001 From: Carlos Schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Mon, 26 Aug 2024 10:57:18 +0200 Subject: [PATCH 01/17] Clean up code --- .../main/java/de/fraunhofer/iosb/client/ClientEndpoint.java | 3 ++- .../java/de/fraunhofer/iosb/client/policy/PolicyService.java | 2 +- .../src/main/java/de/fraunhofer/iosb/app/AasExtension.java | 5 ++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/main/java/de/fraunhofer/iosb/client/ClientEndpoint.java b/client/src/main/java/de/fraunhofer/iosb/client/ClientEndpoint.java index aaf8b4a3..2c08b586 100644 --- a/client/src/main/java/de/fraunhofer/iosb/client/ClientEndpoint.java +++ b/client/src/main/java/de/fraunhofer/iosb/client/ClientEndpoint.java @@ -152,7 +152,8 @@ public Response negotiateContract(@QueryParam("providerUrl") URL counterPartyUrl @QueryParam("assetId") String assetId, DataAddress dataAddress) { monitor.info("POST /%s".formatted(NEGOTIATE_PATH)); - if (Objects.isNull(counterPartyUrl) || Objects.isNull(counterPartyId) || Objects.isNull(assetId)) { + if (counterPartyUrl == null || counterPartyId == null || assetId == null || + assetId.isEmpty()) { return Response.status(Response.Status.BAD_REQUEST) .entity(MISSING_QUERY_PARAMETER_MESSAGE.formatted("providerUrl, counterPartyId, assetId")).build(); } diff --git a/client/src/main/java/de/fraunhofer/iosb/client/policy/PolicyService.java b/client/src/main/java/de/fraunhofer/iosb/client/policy/PolicyService.java index 43ff9c3d..68d8e6ae 100644 --- a/client/src/main/java/de/fraunhofer/iosb/client/policy/PolicyService.java +++ b/client/src/main/java/de/fraunhofer/iosb/client/policy/PolicyService.java @@ -114,7 +114,7 @@ Dataset getDatasetForAssetId(@NotNull String counterPartyId, @NotNull URL counte if (Objects.isNull(datasets) || datasets.size() != 1) { throw new AmbiguousOrNullException( - format("Multiple or no policyDefinitions were found for assetId %s!", + format("Multiple or no policyDefinitions were received for assetId %s!", assetId)); } diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/AasExtension.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/AasExtension.java index f1716301..d6210ad9 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/AasExtension.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/AasExtension.java @@ -35,7 +35,6 @@ import de.fraunhofer.iosb.app.model.aas.service.ServiceRepositoryUpdater; import de.fraunhofer.iosb.app.model.configuration.Configuration; import de.fraunhofer.iosb.app.pipeline.Pipeline; -import de.fraunhofer.iosb.app.pipeline.PipelineFailure; import de.fraunhofer.iosb.app.pipeline.PipelineStep; import de.fraunhofer.iosb.app.pipeline.helper.CollectionFeeder; import de.fraunhofer.iosb.app.pipeline.helper.InputOutputZipper; @@ -61,6 +60,7 @@ import java.util.Objects; import static de.fraunhofer.iosb.app.controller.SelfDescriptionController.SELF_DESCRIPTION_PATH; +import static de.fraunhofer.iosb.app.pipeline.PipelineFailure.Type.FATAL; import static de.fraunhofer.iosb.app.util.InetTools.pingHost; /** @@ -136,7 +136,7 @@ public void initialize(ServiceExtensionContext context) { .step(new MapValueProcessor<>( new EnvironmentToAssetMapper(() -> Configuration.getInstance().isOnlySubmodels()), // Remove fatal results from further processing - result -> result.failed() && result.getFailure().getFailureType().equals(PipelineFailure.Type.FATAL) ? null : result) + result -> result.failed() && FATAL.equals(result.getFailure().getFailureType()) ? null : result) ) .step(PipelineStep.create(registriesMap -> (Collection) registriesMap.entrySet().stream() .map(registry -> new Registry(registry.getKey(), registry.getValue())) @@ -208,7 +208,6 @@ private void registerAasServicesByConfig(ServiceRepository selfDescriptionReposi @Override public void shutdown() { // Gracefully stop AAS services - monitor.info("Stopping all internally started AAS services"); aasController.stopServices(); } } From b404e4172e0e9d9f8afe966429160f726f48f65f Mon Sep 17 00:00:00 2001 From: Carlos Schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Mon, 26 Aug 2024 10:57:44 +0200 Subject: [PATCH 02/17] Add AAS authentication types --- .../iosb/model/auth/AuthenticationMethod.java | 36 ++++++++++++++ .../iosb/model/auth/impl/ApiKey.java | 47 +++++++++++++++++++ .../iosb/model/auth/impl/BasicAuth.java | 44 +++++++++++++++++ .../iosb/model/auth/impl/NoAuth.java | 33 +++++++++++++ 4 files changed, 160 insertions(+) create mode 100644 data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/AuthenticationMethod.java create mode 100644 data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/impl/ApiKey.java create mode 100644 data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/impl/BasicAuth.java create mode 100644 data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/impl/NoAuth.java diff --git a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/AuthenticationMethod.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/AuthenticationMethod.java new file mode 100644 index 00000000..130c5796 --- /dev/null +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/AuthenticationMethod.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige + * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten + * Forschung e.V. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.fraunhofer.iosb.model.auth; + +import org.jetbrains.annotations.Nullable; + +import java.util.AbstractMap; +import java.util.Map; + +public abstract class AuthenticationMethod { + + /** + * Get the header value to add to the request headers to communicate with the service. + * Headers: [... , (getHeader().key, getHeader().value), ...] + * + * @return The header to place in the request in order to authenticate + */ + public @Nullable Map.Entry getHeader() { + return new AbstractMap.SimpleEntry<>("Authorization", getValue()); + } + + protected abstract String getValue(); +} diff --git a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/impl/ApiKey.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/impl/ApiKey.java new file mode 100644 index 00000000..5545ab69 --- /dev/null +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/impl/ApiKey.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige + * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten + * Forschung e.V. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.fraunhofer.iosb.model.auth.impl; + +import de.fraunhofer.iosb.model.auth.AuthenticationMethod; +import org.jetbrains.annotations.Nullable; + +import java.util.AbstractMap; +import java.util.Map; + +/** + * Api key authentication: (key, value). + * Example: (x-api-key,password) + */ +public class ApiKey extends AuthenticationMethod { + + private final String key; + private final String keyValue; + + public ApiKey(String key, String keyValue) { + this.key = key; + this.keyValue = keyValue; + } + + @Override + public @Nullable Map.Entry getHeader() { + return new AbstractMap.SimpleEntry<>(key, getValue()); + } + + @Override + protected String getValue() { + return keyValue; + } +} diff --git a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/impl/BasicAuth.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/impl/BasicAuth.java new file mode 100644 index 00000000..eba21d2f --- /dev/null +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/impl/BasicAuth.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige + * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten + * Forschung e.V. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.fraunhofer.iosb.model.auth.impl; + +import de.fraunhofer.iosb.model.auth.AuthenticationMethod; +import okhttp3.Credentials; +import org.jetbrains.annotations.Nullable; + +import java.util.AbstractMap; +import java.util.Base64; +import java.util.Map; + +/** + * rfc7617 + */ +public class BasicAuth extends AuthenticationMethod { + + private final Base64.Encoder encoder = Base64.getEncoder(); + + private final String username; + private final String password; + + public BasicAuth(String username, String password) { + this.username = username; + this.password = password; + } + + protected String getValue() { + return "Basic %s".formatted(encoder.encodeToString("%s:%s".formatted(username, password).getBytes())); + } +} diff --git a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/impl/NoAuth.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/impl/NoAuth.java new file mode 100644 index 00000000..f1556c94 --- /dev/null +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/impl/NoAuth.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige + * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten + * Forschung e.V. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.fraunhofer.iosb.model.auth.impl; + +import de.fraunhofer.iosb.model.auth.AuthenticationMethod; + +import java.util.Map; + +public class NoAuth extends AuthenticationMethod { + + @Override + public Map.Entry getHeader() { + return null; + } + + @Override + protected String getValue() { + return null; + } +} From 094e11884e150458bde276f1d5928243b658340e Mon Sep 17 00:00:00 2001 From: Carlos Schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Mon, 26 Aug 2024 11:03:04 +0200 Subject: [PATCH 03/17] Move AasAccessUrl to aas-data-plane --- .../iosb/model/aas/AasAccessUrl.java | 48 +++++++++++++++++++ .../de/fraunhofer/iosb/app/AasExtension.java | 2 +- .../app/aas/EnvironmentToAssetMapper.java | 2 +- .../iosb/app/aas/FaaastServiceManager.java | 2 +- .../app/aas/agent/impl/RegistryAgent.java | 2 +- .../iosb/app/model/aas/registry/Registry.java | 2 +- .../aas/registry/RegistryRepository.java | 2 +- .../app/aas/EnvironmentToAssetMapperTest.java | 2 +- .../app/aas/agent/impl/RegistryAgentTest.java | 2 +- 9 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/AasAccessUrl.java diff --git a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/AasAccessUrl.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/AasAccessUrl.java new file mode 100644 index 00000000..945f6135 --- /dev/null +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/AasAccessUrl.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige + * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten + * Forschung e.V. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.fraunhofer.iosb.model.aas; + +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Objects; + +/** + * URL wrapper with equals method appropriate for AAS service access URLs + */ +public record AasAccessUrl(URL url) { + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AasAccessUrl that = (AasAccessUrl) o; + + try { + return Objects.equals(url.toURI(), that.url.toURI()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + @Override + public int hashCode() { + return Objects.hashCode(url); + } + + @Override + public String toString() { + return url.toString(); + } +} diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/AasExtension.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/AasExtension.java index d6210ad9..675276d8 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/AasExtension.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/AasExtension.java @@ -27,7 +27,6 @@ import de.fraunhofer.iosb.app.edc.CleanUpService; import de.fraunhofer.iosb.app.edc.asset.AssetRegistrar; import de.fraunhofer.iosb.app.edc.contract.ContractRegistrar; -import de.fraunhofer.iosb.app.model.aas.AasAccessUrl; import de.fraunhofer.iosb.app.model.aas.registry.Registry; import de.fraunhofer.iosb.app.model.aas.registry.RegistryRepository; import de.fraunhofer.iosb.app.model.aas.registry.RegistryRepositoryUpdater; @@ -41,6 +40,7 @@ import de.fraunhofer.iosb.app.pipeline.helper.MapValueProcessor; import de.fraunhofer.iosb.app.sync.Synchronizer; import de.fraunhofer.iosb.app.util.VariableRateScheduler; +import de.fraunhofer.iosb.model.aas.AasAccessUrl; import de.fraunhofer.iosb.registry.AasServiceRegistry; import org.eclipse.edc.connector.controlplane.asset.spi.index.AssetIndex; import org.eclipse.edc.connector.controlplane.contract.spi.offer.store.ContractDefinitionStore; diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapper.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapper.java index 2000a333..6f93312b 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapper.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapper.java @@ -15,12 +15,12 @@ */ package de.fraunhofer.iosb.app.aas; -import de.fraunhofer.iosb.app.model.aas.AasAccessUrl; import de.fraunhofer.iosb.app.model.aas.service.Service; import de.fraunhofer.iosb.app.pipeline.PipelineFailure; import de.fraunhofer.iosb.app.pipeline.PipelineResult; import de.fraunhofer.iosb.app.pipeline.PipelineStep; import de.fraunhofer.iosb.dataplane.aas.spi.AasDataAddress; +import de.fraunhofer.iosb.model.aas.AasAccessUrl; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; import org.eclipse.digitaltwin.aas4j.v3.model.Environment; diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/FaaastServiceManager.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/FaaastServiceManager.java index 61247077..c8461329 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/FaaastServiceManager.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/FaaastServiceManager.java @@ -15,7 +15,6 @@ */ package de.fraunhofer.iosb.app.aas; -import de.fraunhofer.iosb.app.model.aas.AasAccessUrl; import de.fraunhofer.iosb.ilt.faaast.service.Service; import de.fraunhofer.iosb.ilt.faaast.service.assetconnection.AssetConnectionException; import de.fraunhofer.iosb.ilt.faaast.service.config.ServiceConfig; @@ -25,6 +24,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; import de.fraunhofer.iosb.ilt.faaast.service.persistence.memory.PersistenceInMemoryConfig; import de.fraunhofer.iosb.ilt.faaast.service.starter.util.ServiceConfigHelper; +import de.fraunhofer.iosb.model.aas.AasAccessUrl; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.monitor.Monitor; import org.jetbrains.annotations.NotNull; diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/agent/impl/RegistryAgent.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/agent/impl/RegistryAgent.java index b996ed35..15cc9239 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/agent/impl/RegistryAgent.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/agent/impl/RegistryAgent.java @@ -17,9 +17,9 @@ import de.fraunhofer.iosb.aas.AasDataProcessorFactory; import de.fraunhofer.iosb.app.aas.agent.AasAgent; -import de.fraunhofer.iosb.app.model.aas.AasAccessUrl; import de.fraunhofer.iosb.app.pipeline.PipelineFailure; import de.fraunhofer.iosb.app.pipeline.PipelineResult; +import de.fraunhofer.iosb.model.aas.AasAccessUrl; import de.fraunhofer.iosb.registry.AasServiceRegistry; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShellDescriptor; import org.eclipse.digitaltwin.aas4j.v3.model.Endpoint; diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/Registry.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/Registry.java index 33fc58fc..908ded94 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/Registry.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/Registry.java @@ -15,8 +15,8 @@ */ package de.fraunhofer.iosb.app.model.aas.registry; -import de.fraunhofer.iosb.app.model.aas.AasAccessUrl; import de.fraunhofer.iosb.app.model.aas.service.Service; +import de.fraunhofer.iosb.model.aas.AasAccessUrl; import java.util.Collection; import java.util.Objects; diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/RegistryRepository.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/RegistryRepository.java index e90c275b..668fc68b 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/RegistryRepository.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/RegistryRepository.java @@ -15,9 +15,9 @@ */ package de.fraunhofer.iosb.app.model.aas.registry; -import de.fraunhofer.iosb.app.model.aas.AasAccessUrl; import de.fraunhofer.iosb.app.model.aas.service.Service; import de.fraunhofer.iosb.app.model.ids.SelfDescriptionChangeListener; +import de.fraunhofer.iosb.model.aas.AasAccessUrl; import org.eclipse.edc.spi.observe.ObservableImpl; import java.net.URL; diff --git a/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapperTest.java b/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapperTest.java index 723516c0..5132b452 100644 --- a/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapperTest.java +++ b/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapperTest.java @@ -15,10 +15,10 @@ */ package de.fraunhofer.iosb.app.aas; -import de.fraunhofer.iosb.app.model.aas.AasAccessUrl; import de.fraunhofer.iosb.app.model.aas.service.Service; import de.fraunhofer.iosb.app.pipeline.PipelineFailure; import de.fraunhofer.iosb.dataplane.aas.spi.AasDataAddress; +import de.fraunhofer.iosb.model.aas.AasAccessUrl; import de.fraunhofer.iosb.util.Encoder; import org.eclipse.digitaltwin.aas4j.v3.model.Environment; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultEnvironment; diff --git a/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/agent/impl/RegistryAgentTest.java b/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/agent/impl/RegistryAgentTest.java index dbdbe089..9a89affc 100644 --- a/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/agent/impl/RegistryAgentTest.java +++ b/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/agent/impl/RegistryAgentTest.java @@ -16,7 +16,7 @@ package de.fraunhofer.iosb.app.aas.agent.impl; import de.fraunhofer.iosb.aas.impl.AllAasDataProcessorFactory; -import de.fraunhofer.iosb.app.model.aas.AasAccessUrl; +import de.fraunhofer.iosb.model.aas.AasAccessUrl; import de.fraunhofer.iosb.registry.AasServiceRegistry; import de.fraunhofer.iosb.ssl.impl.NoOpSelfSignedCertificateRetriever; import dev.failsafe.RetryPolicy; From 893c04551a3fb855c1d986e382d3b0ac6beea8cf Mon Sep 17 00:00:00 2001 From: Carlos Schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Mon, 26 Aug 2024 15:10:34 +0200 Subject: [PATCH 04/17] Improve exception message --- .../java/de/fraunhofer/iosb/client/policy/PolicyService.java | 3 ++- .../de/fraunhofer/iosb/client/policy/PolicyServiceTest.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/client/src/main/java/de/fraunhofer/iosb/client/policy/PolicyService.java b/client/src/main/java/de/fraunhofer/iosb/client/policy/PolicyService.java index 68d8e6ae..1f0fde35 100644 --- a/client/src/main/java/de/fraunhofer/iosb/client/policy/PolicyService.java +++ b/client/src/main/java/de/fraunhofer/iosb/client/policy/PolicyService.java @@ -58,6 +58,7 @@ class PolicyService { private static final String CATALOG_RETRIEVAL_FAILURE_MSG = "Catalog by provider %s couldn't be retrieved: %s"; + public static final String AMBIGUOUS_OR_NULL_MESSAGE = "Multiple or no policyDefinitions were received for assetId %s!"; private final CatalogService catalogService; private final TypeTransformerRegistry transformer; @@ -114,7 +115,7 @@ Dataset getDatasetForAssetId(@NotNull String counterPartyId, @NotNull URL counte if (Objects.isNull(datasets) || datasets.size() != 1) { throw new AmbiguousOrNullException( - format("Multiple or no policyDefinitions were received for assetId %s!", + format(AMBIGUOUS_OR_NULL_MESSAGE, assetId)); } diff --git a/client/src/test/java/de/fraunhofer/iosb/client/policy/PolicyServiceTest.java b/client/src/test/java/de/fraunhofer/iosb/client/policy/PolicyServiceTest.java index e768a196..fe7a7dea 100644 --- a/client/src/test/java/de/fraunhofer/iosb/client/policy/PolicyServiceTest.java +++ b/client/src/test/java/de/fraunhofer/iosb/client/policy/PolicyServiceTest.java @@ -42,6 +42,7 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; +import static de.fraunhofer.iosb.client.policy.PolicyService.AMBIGUOUS_OR_NULL_MESSAGE; import static java.lang.String.format; import static org.eclipse.edc.protocol.dsp.http.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP; import static org.eclipse.edc.spi.query.Criterion.criterion; @@ -128,7 +129,7 @@ void getDatasetNoDatasetsTest() throws InterruptedException { policyService.getDatasetForAssetId("test-counter-party-id", testUrl, "test-asset-id"); fail(); // Should throw exception } catch (AmbiguousOrNullException expected) { - assertEquals(format("Multiple or no policyDefinitions were found for assetId %s!", "test-asset-id"), expected.getMessage()); + assertEquals(AMBIGUOUS_OR_NULL_MESSAGE.formatted("test-asset-id"), expected.getMessage()); } } From 90f7a09d337d78943719b001d1cde5670f9e606d Mon Sep 17 00:00:00 2001 From: Carlos Schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Mon, 26 Aug 2024 15:12:02 +0200 Subject: [PATCH 05/17] Add boilerplate auth elements --- .../fraunhofer/iosb/aas/AasDataProcessor.java | 12 +++- .../aas/pipeline/AasDataSinkFactory.java | 1 - .../dataplane/aas/spi/AasDataAddress.java | 64 ++++++++++++----- .../iosb/model/aas/AasProvider.java | 70 +++++++++++++++++++ .../{ => aas}/auth/AuthenticationMethod.java | 2 +- .../model/{ => aas}/auth/impl/ApiKey.java | 4 +- .../model/{ => aas}/auth/impl/BasicAuth.java | 8 +-- .../model/{ => aas}/auth/impl/NoAuth.java | 4 +- .../model/aas/{ => net}/AasAccessUrl.java | 2 +- .../iosb/app/model/aas/AasAccessUrl.java | 48 ------------- 10 files changed, 134 insertions(+), 81 deletions(-) create mode 100644 data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/AasProvider.java rename data-plane-aas/src/main/java/de/fraunhofer/iosb/model/{ => aas}/auth/AuthenticationMethod.java (96%) rename data-plane-aas/src/main/java/de/fraunhofer/iosb/model/{ => aas}/auth/impl/ApiKey.java (92%) rename data-plane-aas/src/main/java/de/fraunhofer/iosb/model/{ => aas}/auth/impl/BasicAuth.java (85%) rename data-plane-aas/src/main/java/de/fraunhofer/iosb/model/{ => aas}/auth/impl/NoAuth.java (89%) rename data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/{ => net}/AasAccessUrl.java (97%) delete mode 100644 edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/AasAccessUrl.java diff --git a/data-plane-aas/src/main/java/de/fraunhofer/iosb/aas/AasDataProcessor.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/aas/AasDataProcessor.java index 7c6a972d..1ff4243c 100644 --- a/data-plane-aas/src/main/java/de/fraunhofer/iosb/aas/AasDataProcessor.java +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/aas/AasDataProcessor.java @@ -81,11 +81,17 @@ public Response send(@NotNull AasDataAddress aasDataAddress, @NotNull Part part) private Response send(AasDataAddress aasDataAddress, byte[] bytes, String mediaType) throws IOException { var requestUrlBuilder = HttpUrl.get(aasDataAddress.getBaseUrl()).newBuilder(); - if (!aasDataAddress.referenceChainAsPath().isEmpty()) { - requestUrlBuilder.addPathSegments(aasDataAddress.referenceChainAsPath()); + + var requestPath = aasDataAddress.getPath(); + + + if (!requestPath.isEmpty()) { + // Remove leading forward slash + requestPath = requestPath.startsWith("/") ? requestPath.substring(1) : requestPath; + requestUrlBuilder.addPathSegments(requestPath); } - var requestUrl = requestUrlBuilder.build().url(); + var requestUrl = requestUrlBuilder.build().url(); var requestBody = new AasTransferRequestBody(bytes, mediaType); var request = new Request.Builder() diff --git a/data-plane-aas/src/main/java/de/fraunhofer/iosb/dataplane/aas/pipeline/AasDataSinkFactory.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/dataplane/aas/pipeline/AasDataSinkFactory.java index 3d84af38..4a6697ac 100644 --- a/data-plane-aas/src/main/java/de/fraunhofer/iosb/dataplane/aas/pipeline/AasDataSinkFactory.java +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/dataplane/aas/pipeline/AasDataSinkFactory.java @@ -54,7 +54,6 @@ public DataSink createSink(DataFlowStartMessage request) { .monitor(monitor) .aasDataAddress(dataAddress) .build(); - } @Override diff --git a/data-plane-aas/src/main/java/de/fraunhofer/iosb/dataplane/aas/spi/AasDataAddress.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/dataplane/aas/spi/AasDataAddress.java index adc42f2a..4cca534d 100644 --- a/data-plane-aas/src/main/java/de/fraunhofer/iosb/dataplane/aas/spi/AasDataAddress.java +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/dataplane/aas/spi/AasDataAddress.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import de.fraunhofer.iosb.model.aas.AasProvider; import de.fraunhofer.iosb.util.Encoder; import org.eclipse.digitaltwin.aas4j.v3.model.Reference; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultReference; @@ -27,6 +28,7 @@ import org.eclipse.edc.spi.types.domain.DataAddress; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -44,12 +46,13 @@ @JsonDeserialize(builder = DataAddress.Builder.class) public class AasDataAddress extends DataAddress { - public static final String REFERENCE_CHAIN = "referenceChain"; public static final String BASE_URL = "https://w3id.org/edc/v0.0.1/ns/baseUrl"; private static final String ADDITIONAL_HEADER = "header:"; private static final String METHOD = "method"; - private static final String QUERY_PARAMS = "queryParams"; + private static final String PROVIDER = "AAS-Provider"; + private static final String REFERENCE_CHAIN = "referenceChain"; + private static final String PATH = "PATH"; private AasDataAddress() { super(); @@ -58,7 +61,19 @@ private AasDataAddress() { @JsonIgnore public String getBaseUrl() { - return getStringProperty(BASE_URL); + return hasProvider() ? getProvider().getAccessUrl().toString() : getStringProperty(BASE_URL); + } + + private AasProvider getProvider() { + Object provider = super.getProperties().get(PROVIDER); + if (provider instanceof AasProvider) { + return (AasProvider) provider; + } + throw new EdcException(new IllegalStateException("Provider not set correctly: %s".formatted(provider))); + } + + private boolean hasProvider() { + return getProperties().get(PROVIDER) != null; } @JsonIgnore @@ -68,29 +83,37 @@ public String getMethod() { @JsonIgnore public Map getAdditionalHeaders() { - return getProperties().entrySet().stream() + // First get authentication headers from aas provider, then additional ones + Map headers = hasProvider() ? getProvider().getHeaders() : new HashMap<>(); + headers.putAll(getProperties().entrySet().stream() .filter(entry -> entry.getKey().startsWith(ADDITIONAL_HEADER)) - .collect(toMap(headerName -> headerName.getKey().replace(ADDITIONAL_HEADER, ""), headerValue -> (String) headerValue.getValue())); + .collect(toMap(headerName -> headerName.getKey().replace(ADDITIONAL_HEADER, ""), + headerValue -> (String) headerValue.getValue()))); + return headers; } /** - * Builds and returns the HTTP URL path required to access this AAS data at the AAS service. + * If an explicit path is available, return this path. Else, return the following: + *

+ * build and returns the HTTP URL path required to access this AAS data at the AAS service. * Example: ReferenceChain: [Submodel x, SubmodelElementCollection y, SubmodelElement z] * --> path: submodels/base64(x)/submodel-elements/y.z * - * @return Path correlating to reference chain stored in this DataAddress (no leading '/'). + * @return Explicitly defined path or path correlating to reference chain stored in this DataAddress (no leading '/'). */ - public String referenceChainAsPath() { + public String getPath() { + return getStringProperty(PATH, referenceChainAsPath()); + } + + private String referenceChainAsPath() { StringBuilder urlBuilder = new StringBuilder(); for (var key : getReferenceChain().getKeys()) { switch (key.getType()) { - case ASSET_ADMINISTRATION_SHELL -> - urlBuilder.append("shells/").append(Encoder.encodeBase64(key.getValue())); + case ASSET_ADMINISTRATION_SHELL -> urlBuilder.append("shells/").append(Encoder.encodeBase64(key.getValue())); case SUBMODEL -> urlBuilder.append("submodels/").append(Encoder.encodeBase64(key.getValue())); - case CONCEPT_DESCRIPTION -> - urlBuilder.append("concept-descriptions/").append(Encoder.encodeBase64(key.getValue())); + case CONCEPT_DESCRIPTION -> urlBuilder.append("concept-descriptions/").append(Encoder.encodeBase64(key.getValue())); case SUBMODEL_ELEMENT, SUBMODEL_ELEMENT_COLLECTION, SUBMODEL_ELEMENT_LIST -> { if (urlBuilder.indexOf("/submodel-elements/") == -1) { urlBuilder.append("/submodel-elements/"); @@ -99,8 +122,7 @@ public String referenceChainAsPath() { } urlBuilder.append(key.getValue()); } - default -> - throw new EdcException(new IllegalStateException(format("Element type not recognized in AasDataAddress: %s", key.getType()))); + default -> throw new EdcException(new IllegalStateException(format("Element type not recognized in AasDataAddress: %s", key.getType()))); } } @@ -134,13 +156,18 @@ public static Builder newInstance() { return new Builder(); } + public Builder aasProvider(AasProvider provider) { + this.property(PROVIDER, provider); + return this; + } + public Builder baseUrl(String baseUrl) { this.property(BASE_URL, baseUrl); return this; } - public Builder queryParams(String queryParams) { - this.property(QUERY_PARAMS, queryParams); + public Builder path(String path) { + this.property(PATH, path); return this; } @@ -157,7 +184,10 @@ public Builder referenceChain(Reference referenceChain) { } public Builder copyFrom(DataAddress other) { - (Optional.ofNullable(other).map(DataAddress::getProperties).orElse(Collections.emptyMap())).forEach(this::property); + (Optional.ofNullable(other) + .map(DataAddress::getProperties) + .orElse(Collections.emptyMap())) + .forEach(this::property); return this; } diff --git a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/AasProvider.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/AasProvider.java new file mode 100644 index 00000000..76f7e2e3 --- /dev/null +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/AasProvider.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige + * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten + * Forschung e.V. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.fraunhofer.iosb.model.aas; + +import de.fraunhofer.iosb.model.aas.auth.AuthenticationMethod; +import de.fraunhofer.iosb.model.aas.auth.impl.NoAuth; +import de.fraunhofer.iosb.model.aas.net.AasAccessUrl; + +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public abstract class AasProvider { + + public static final String AAS_V3_PREFIX = "/api/v3.0"; + + private final AasAccessUrl url; + private final AuthenticationMethod authentication; + + public AasProvider(AasAccessUrl url) { + this.url = url; + this.authentication = new NoAuth(); + } + + public AasProvider(AasAccessUrl url, AuthenticationMethod authentication) { + this.url = url; + this.authentication = authentication; + } + + public Map getHeaders() { + var header = authentication.getHeader(); + var responseMap = new HashMap(); + if (header != null) { + responseMap.put(header.getKey(), header.getValue()); + } + + return responseMap; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AasProvider that = (AasProvider) o; + return Objects.equals(url, that.url); + } + + @Override + public int hashCode() { + return Objects.hashCode(url); + } + + public URL getAccessUrl() { + return url.url(); + } +} diff --git a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/AuthenticationMethod.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/AuthenticationMethod.java similarity index 96% rename from data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/AuthenticationMethod.java rename to data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/AuthenticationMethod.java index 130c5796..ef7df223 100644 --- a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/AuthenticationMethod.java +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/AuthenticationMethod.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.model.auth; +package de.fraunhofer.iosb.model.aas.auth; import org.jetbrains.annotations.Nullable; diff --git a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/impl/ApiKey.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/impl/ApiKey.java similarity index 92% rename from data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/impl/ApiKey.java rename to data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/impl/ApiKey.java index 5545ab69..837997fd 100644 --- a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/impl/ApiKey.java +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/impl/ApiKey.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.model.auth.impl; +package de.fraunhofer.iosb.model.aas.auth.impl; -import de.fraunhofer.iosb.model.auth.AuthenticationMethod; +import de.fraunhofer.iosb.model.aas.auth.AuthenticationMethod; import org.jetbrains.annotations.Nullable; import java.util.AbstractMap; diff --git a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/impl/BasicAuth.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/impl/BasicAuth.java similarity index 85% rename from data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/impl/BasicAuth.java rename to data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/impl/BasicAuth.java index eba21d2f..773b706c 100644 --- a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/impl/BasicAuth.java +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/impl/BasicAuth.java @@ -13,15 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.model.auth.impl; +package de.fraunhofer.iosb.model.aas.auth.impl; -import de.fraunhofer.iosb.model.auth.AuthenticationMethod; -import okhttp3.Credentials; -import org.jetbrains.annotations.Nullable; +import de.fraunhofer.iosb.model.aas.auth.AuthenticationMethod; -import java.util.AbstractMap; import java.util.Base64; -import java.util.Map; /** * rfc7617 diff --git a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/impl/NoAuth.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/impl/NoAuth.java similarity index 89% rename from data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/impl/NoAuth.java rename to data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/impl/NoAuth.java index f1556c94..7b7ceb95 100644 --- a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/auth/impl/NoAuth.java +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/impl/NoAuth.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.model.auth.impl; +package de.fraunhofer.iosb.model.aas.auth.impl; -import de.fraunhofer.iosb.model.auth.AuthenticationMethod; +import de.fraunhofer.iosb.model.aas.auth.AuthenticationMethod; import java.util.Map; diff --git a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/AasAccessUrl.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/net/AasAccessUrl.java similarity index 97% rename from data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/AasAccessUrl.java rename to data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/net/AasAccessUrl.java index 945f6135..38a132b7 100644 --- a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/AasAccessUrl.java +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/net/AasAccessUrl.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.iosb.model.aas; +package de.fraunhofer.iosb.model.aas.net; import java.net.URISyntaxException; import java.net.URL; diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/AasAccessUrl.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/AasAccessUrl.java deleted file mode 100644 index e61f8613..00000000 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/AasAccessUrl.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige - * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten - * Forschung e.V. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.iosb.app.model.aas; - -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Objects; - -/** - * URL wrapper with equals method appropriate for AAS service access URLs - */ -public record AasAccessUrl(URL url) { - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - AasAccessUrl that = (AasAccessUrl) o; - - try { - return Objects.equals(url.toURI(), that.url.toURI()); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } - - @Override - public int hashCode() { - return Objects.hashCode(url); - } - - @Override - public String toString() { - return url.toString(); - } -} From 20ff82d95118aa2ed0679e8467dc3b7a3e3989c2 Mon Sep 17 00:00:00 2001 From: Carlos Schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Mon, 26 Aug 2024 15:14:27 +0200 Subject: [PATCH 06/17] Apply updates to pipeline and elements --- .../de/fraunhofer/iosb/app/AasExtension.java | 29 +++--- .../app/aas/EnvironmentToAssetMapper.java | 70 ++++++++------ .../iosb/app/aas/FaaastServiceManager.java | 2 +- .../iosb/app/aas/agent/AasAgent.java | 49 +++++----- .../app/aas/agent/impl/RegistryAgent.java | 43 ++++----- .../iosb/app/aas/agent/impl/ServiceAgent.java | 27 ++---- .../iosb/app/controller/AasController.java | 10 +- .../iosb/app/model/aas/registry/Registry.java | 91 ++++++++++++++++--- .../aas/registry/RegistryRepository.java | 17 ++-- .../registry/RegistryRepositoryUpdater.java | 4 +- .../iosb/app/model/aas/service/Service.java | 77 ++++++++++++---- .../model/aas/service/ServiceRepository.java | 13 ++- .../aas/service/ServiceRepositoryUpdater.java | 2 +- .../iosb/app/pipeline/helper/Filter.java | 41 +++++++++ .../fraunhofer/iosb/app/util/InetTools.java | 9 ++ .../app/aas/EnvironmentToAssetMapperTest.java | 42 ++++----- .../iosb/app/aas/agent/AasAgentTest.java | 5 +- .../app/aas/agent/impl/RegistryAgentTest.java | 27 +++--- .../app/aas/agent/impl/ServiceAgentTest.java | 17 ++-- .../iosb/app/sync/SynchronizerTest.java | 9 +- 20 files changed, 357 insertions(+), 227 deletions(-) create mode 100644 edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/pipeline/helper/Filter.java diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/AasExtension.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/AasExtension.java index 675276d8..b247dc24 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/AasExtension.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/AasExtension.java @@ -36,11 +36,12 @@ import de.fraunhofer.iosb.app.pipeline.Pipeline; import de.fraunhofer.iosb.app.pipeline.PipelineStep; import de.fraunhofer.iosb.app.pipeline.helper.CollectionFeeder; +import de.fraunhofer.iosb.app.pipeline.helper.Filter; import de.fraunhofer.iosb.app.pipeline.helper.InputOutputZipper; import de.fraunhofer.iosb.app.pipeline.helper.MapValueProcessor; import de.fraunhofer.iosb.app.sync.Synchronizer; +import de.fraunhofer.iosb.app.util.InetTools; import de.fraunhofer.iosb.app.util.VariableRateScheduler; -import de.fraunhofer.iosb.model.aas.AasAccessUrl; import de.fraunhofer.iosb.registry.AasServiceRegistry; import org.eclipse.edc.connector.controlplane.asset.spi.index.AssetIndex; import org.eclipse.edc.connector.controlplane.contract.spi.offer.store.ContractDefinitionStore; @@ -58,10 +59,10 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.Function; import static de.fraunhofer.iosb.app.controller.SelfDescriptionController.SELF_DESCRIPTION_PATH; import static de.fraunhofer.iosb.app.pipeline.PipelineFailure.Type.FATAL; -import static de.fraunhofer.iosb.app.util.InetTools.pingHost; /** * EDC Extension supporting usage of Asset Administration Shells. @@ -103,20 +104,11 @@ public void initialize(ServiceExtensionContext context) { serviceRepository.registerListener(aasController); registryRepository.registerListener(aasController); - // Check if a URL is reachable by pinging the host+port combination (not actual ICMP) - PipelineStep reachabilityCheck = PipelineStep.create(url -> { - if (!pingHost(url.getHost(), url.getPort(), 10)) { - monitor.severe("URL %s not reachable!".formatted(url)); - return null; - } - return url; - }); - var serviceSynchronization = new Pipeline.Builder() .monitor(monitor.withPrefix("Service Synchronization Pipeline")) - .supplier(serviceRepository::getAllServiceAccessUrls) - .step(new CollectionFeeder<>(reachabilityCheck)) - .step(new InputOutputZipper<>(new ServiceAgent(aasDataProcessorFactory), AasAccessUrl::new)) + .supplier(serviceRepository::getAll) + .step(new Filter<>(InetTools::pingHost)) + .step(new InputOutputZipper<>(new ServiceAgent(aasDataProcessorFactory), Function.identity())) .step(new EnvironmentToAssetMapper(() -> Configuration.getInstance().isOnlySubmodels())) .step(new CollectionFeeder<>(new ServiceRepositoryUpdater(serviceRepository))) .step(new Synchronizer()) @@ -130,16 +122,17 @@ public void initialize(ServiceExtensionContext context) { var registrySynchronization = new Pipeline.Builder() .monitor(monitor.withPrefix("Registry Synchronization Pipeline")) - .supplier(registryRepository::getAllUrls) - .step(new CollectionFeeder<>(reachabilityCheck)) - .step(new InputOutputZipper<>(new RegistryAgent(aasDataProcessorFactory, foreignServerRegistry), AasAccessUrl::new)) + .supplier(registryRepository::getAll) + .step(new Filter<>(InetTools::pingHost)) + .step(new InputOutputZipper<>(new RegistryAgent(aasDataProcessorFactory, foreignServerRegistry), + Function.identity())) .step(new MapValueProcessor<>( new EnvironmentToAssetMapper(() -> Configuration.getInstance().isOnlySubmodels()), // Remove fatal results from further processing result -> result.failed() && FATAL.equals(result.getFailure().getFailureType()) ? null : result) ) .step(PipelineStep.create(registriesMap -> (Collection) registriesMap.entrySet().stream() - .map(registry -> new Registry(registry.getKey(), registry.getValue())) + .map(registry -> registry.getKey().with(registry.getValue())) .toList())) .step(new RegistryRepositoryUpdater(registryRepository)) .step(new Synchronizer()) diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapper.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapper.java index 6f93312b..c3721e93 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapper.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapper.java @@ -20,7 +20,6 @@ import de.fraunhofer.iosb.app.pipeline.PipelineResult; import de.fraunhofer.iosb.app.pipeline.PipelineStep; import de.fraunhofer.iosb.dataplane.aas.spi.AasDataAddress; -import de.fraunhofer.iosb.model.aas.AasAccessUrl; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; import org.eclipse.digitaltwin.aas4j.v3.model.Environment; @@ -39,7 +38,7 @@ import org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset; import org.jetbrains.annotations.NotNull; -import java.net.URL; +import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -49,7 +48,6 @@ import java.util.Optional; import java.util.function.Supplier; -import static de.fraunhofer.iosb.app.aas.agent.AasAgent.AAS_V3_PREFIX; import static de.fraunhofer.iosb.app.pipeline.util.PipelineUtils.extractContents; import static de.fraunhofer.iosb.app.pipeline.util.PipelineUtils.handleError; @@ -58,7 +56,8 @@ * This is not a holistic transformation but rather maps some * key elements and creates appropriate data address and assetId. */ -public class EnvironmentToAssetMapper extends PipelineStep, Collection> { +public class EnvironmentToAssetMapper extends PipelineStep, Collection> { + private static final String CONCEPT_DESCRIPTIONS = "conceptDescriptions"; private static final String SHELLS = "shells"; private static final String SUBMODELS = "submodels"; @@ -76,12 +75,11 @@ public EnvironmentToAssetMapper(Supplier onlySubmodelsDecision) { * @return Asset as described above */ @Override - public PipelineResult> apply(Map environments) { + public PipelineResult> apply(Map environments) { var results = environments.entrySet().stream() .map(entry -> executeSingle( Optional.ofNullable(entry.getKey()) - .orElse(new AasAccessUrl(null)) - .url(), + .orElse(new Service(null)), entry.getValue())).toList(); var contents = extractContents(results); @@ -89,40 +87,52 @@ public PipelineResult> apply(Map return Objects.requireNonNullElseGet(handleError(results, contents), () -> PipelineResult.success(contents)); } - public PipelineResult executeSingle(URL accessUrl, Environment environment) { - if (accessUrl == null) { + public PipelineResult executeSingle(Service service, Environment environment) { + if (service == null || service.getAccessUrl() == null) { return PipelineResult.failure(PipelineFailure.fatal( List.of("Mapping failure: accessUrl is null"))); } else if (environment == null) { - return PipelineResult.recoverableFailure(new Service(accessUrl, null), + return PipelineResult.recoverableFailure(service, PipelineFailure.warning(List.of("Mapping failure for accessUrl %s: environment is null" - .formatted(accessUrl)))); + .formatted(service.getAccessUrl())))); } var assetBuilder = Asset.Builder.newInstance(); - // Add the /api/v3.0 prefix to each element since this is AAS spec - var accessUrlV3 = accessUrl.toString().concat(AAS_V3_PREFIX); - // Submodels - assetBuilder.property(SUBMODELS, environment.getSubmodels().stream() - .map(submodel -> mapSubmodelToAsset(submodel, accessUrlV3)) - .toList()); + + try { + final String submodelsUrl = service.getSubmodelsUrl().toString(); + assetBuilder.property(SUBMODELS, environment.getSubmodels().stream() + .map(submodel -> mapSubmodelToAsset(submodel, submodelsUrl)) + .toList()); + } catch (MalformedURLException e) { + return PipelineResult.failure(PipelineFailure.warning(List.of("Could not build access url for %s".formatted(service.getAccessUrl())))); + } if (onlySubmodelsDecision.get()) { assetBuilder.property(SHELLS, List.of()); assetBuilder.property(CONCEPT_DESCRIPTIONS, List.of()); - return PipelineResult.success(new Service(accessUrl, assetBuilder.build())); + return PipelineResult.success(service.with(assetBuilder.build())); } - return PipelineResult.success(new Service(accessUrl, - assetBuilder - .property(SHELLS, - environment.getAssetAdministrationShells().stream() - .map((AssetAdministrationShell shell) -> mapShellToAsset(shell, accessUrlV3)) - .toList()) - .property(CONCEPT_DESCRIPTIONS, - environment.getConceptDescriptions().stream() - .map((ConceptDescription conceptDescription) -> mapConceptDescriptionToAsset(conceptDescription, accessUrlV3)) - .toList()) - .build())); + + try { + var shellsUrl = service.getShellsUrl().toString(); + var conceptDescriptionsUrl = service.getConceptDescriptionsUrl().toString(); + + return PipelineResult.success(service.with( + assetBuilder + .property(SHELLS, + environment.getAssetAdministrationShells().stream() + .map((AssetAdministrationShell shell) -> mapShellToAsset(shell, shellsUrl)) + .toList()) + .property(CONCEPT_DESCRIPTIONS, + environment.getConceptDescriptions().stream() + .map((ConceptDescription conceptDescription) -> mapConceptDescriptionToAsset(conceptDescription, conceptDescriptionsUrl)) + .toList()) + .build())); + } catch (MalformedURLException e) { + return PipelineResult.recoverableFailure(service.with(assetBuilder.build()), PipelineFailure.warning(List.of("Could not build access url for %s".formatted(service.getAccessUrl())))); + } + } private Asset.Builder mapReferableToAssetBuilder(R referable) { @@ -177,7 +187,7 @@ private Asset mapSubmodelElementToAsset(Reference pa } private static @NotNull String getId(String accessUrl, AasDataAddress dataAddress) { - return String.valueOf("%s:%s".formatted(accessUrl, dataAddress.referenceChainAsPath()).hashCode()); + return String.valueOf("%s:%s".formatted(accessUrl, dataAddress.getPath()).hashCode()); } private Collection getContainerElements(T submodelElement) { diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/FaaastServiceManager.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/FaaastServiceManager.java index c8461329..980aa0e5 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/FaaastServiceManager.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/FaaastServiceManager.java @@ -24,7 +24,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.exception.MessageBusException; import de.fraunhofer.iosb.ilt.faaast.service.persistence.memory.PersistenceInMemoryConfig; import de.fraunhofer.iosb.ilt.faaast.service.starter.util.ServiceConfigHelper; -import de.fraunhofer.iosb.model.aas.AasAccessUrl; +import de.fraunhofer.iosb.model.aas.net.AasAccessUrl; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.monitor.Monitor; import org.jetbrains.annotations.NotNull; diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/agent/AasAgent.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/agent/AasAgent.java index ea83fff3..0e9a5072 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/agent/AasAgent.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/agent/AasAgent.java @@ -20,6 +20,7 @@ import de.fraunhofer.iosb.aas.AasDataProcessorFactory; import de.fraunhofer.iosb.app.pipeline.PipelineStep; import de.fraunhofer.iosb.dataplane.aas.spi.AasDataAddress; +import de.fraunhofer.iosb.model.aas.AasProvider; import okhttp3.MediaType; import okhttp3.Request; import okhttp3.Response; @@ -42,9 +43,9 @@ /** * Fetching an AAS environment from AAS service or AAS registry providers. */ -public abstract class AasAgent extends PipelineStep { +public abstract class AasAgent extends PipelineStep { - public static final String AAS_V3_PREFIX = "/api/v3.0"; + //public static final String AAS_V3_PREFIX = "/api/v3.0"; private final AasDataProcessorFactory aasDataProcessorFactory; private final JsonDeserializer jsonDeserializer = new JsonDeserializer(); @@ -54,8 +55,8 @@ public AasAgent(AasDataProcessorFactory aasDataProcessorFactory) { this.aasDataProcessorFactory = aasDataProcessorFactory; } - protected Result> readElements(URL accessUrl, Class clazz) throws IOException { - try (var response = executeRequest(accessUrl)) { + protected Result> readElements(AasProvider provider, URL url, Class clazz) throws IOException { + try (var response = executeRequest(provider, url)) { if (response.isSuccessful() && response.body() != null) { return readList(response.body().string(), clazz); } else if (response.code() > 299 && response.code() < 500) { @@ -66,21 +67,11 @@ protected Result> readElements(URL accessUrl, Class clazz) throws return Result.failure(String.valueOf(response.code())); } } - throw new IllegalStateException("Reading %s from %s failed".formatted(clazz.getName(), accessUrl)); + throw new IllegalStateException("Reading %s from %s failed".formatted(clazz.getName(), provider.getAccessUrl())); } - private @Nonnull Result> readList(@Nullable String serialized, Class clazz) { - try { - var responseJson = objectMapper.readTree(serialized).get("result"); - return Result.success(Optional.ofNullable(jsonDeserializer.readList(responseJson, clazz)) - .orElse(new ArrayList<>())); - } catch (JsonProcessingException | DeserializationException e) { - return Result.failure(List.of("Failed parsing list of %s".formatted(clazz.getName()), e.getMessage())); - } - } - - private @Nonnull Response executeRequest(URL aasServiceUrl) throws IOException { - var processor = aasDataProcessorFactory.processorFor(aasServiceUrl.toString()); + private Response executeRequest(AasProvider provider, URL apply) throws IOException { + var processor = aasDataProcessorFactory.processorFor(provider.getAccessUrl().toString()); if (processor.failed()) { return new Response.Builder() @@ -91,12 +82,22 @@ protected Result> readElements(URL accessUrl, Class clazz) throws .build(); } - return processor.getContent() - .send(AasDataAddress.Builder - .newInstance() - .method(GET) - .baseUrl(aasServiceUrl.toString()) - .build() - ); + var addressBuilder = AasDataAddress.Builder + .newInstance() + .method(GET) + .aasProvider(provider) + .path(apply.getPath()); + + return processor.getContent().send(addressBuilder.build()); + } + + private @Nonnull Result> readList(@Nullable String serialized, Class clazz) { + try { + var responseJson = objectMapper.readTree(serialized).get("result"); + return Result.success(Optional.ofNullable(jsonDeserializer.readList(responseJson, clazz)) + .orElse(new ArrayList<>())); + } catch (JsonProcessingException | DeserializationException e) { + return Result.failure(List.of("Failed parsing list of %s".formatted(clazz.getName()), e.getMessage())); + } } } diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/agent/impl/RegistryAgent.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/agent/impl/RegistryAgent.java index 15cc9239..b4983b7d 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/agent/impl/RegistryAgent.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/agent/impl/RegistryAgent.java @@ -17,9 +17,10 @@ import de.fraunhofer.iosb.aas.AasDataProcessorFactory; import de.fraunhofer.iosb.app.aas.agent.AasAgent; +import de.fraunhofer.iosb.app.model.aas.registry.Registry; +import de.fraunhofer.iosb.app.model.aas.service.Service; import de.fraunhofer.iosb.app.pipeline.PipelineFailure; import de.fraunhofer.iosb.app.pipeline.PipelineResult; -import de.fraunhofer.iosb.model.aas.AasAccessUrl; import de.fraunhofer.iosb.registry.AasServiceRegistry; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShellDescriptor; import org.eclipse.digitaltwin.aas4j.v3.model.Endpoint; @@ -42,7 +43,6 @@ import java.io.IOException; import java.net.MalformedURLException; -import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; @@ -56,10 +56,8 @@ import java.util.stream.Stream; import javax.annotation.Nonnull; -public class RegistryAgent extends AasAgent> { +public class RegistryAgent extends AasAgent> { - public static final String SUBMODEL_DESCRIPTORS_PATH = "%s/submodel-descriptors".formatted(AAS_V3_PREFIX); - public static final String SHELL_DESCRIPTORS_PATH = "%s/shell-descriptors".formatted(AAS_V3_PREFIX); public static final String SHELL_DIRECT_ENDPOINT = "AAS-3.0"; public static final String SUBMODEL_DIRECT_ENDPOINT = "SUBMODEL-3.0"; @@ -77,39 +75,34 @@ public RegistryAgent(AasDataProcessorFactory aasDataProcessorFactory, AasService } @Override - public PipelineResult> apply(@Nonnull URL url) { + public PipelineResult> apply(@Nonnull Registry registry) { try { - return readEnvironment(url); + return readEnvironment(registry); } catch (Exception e) { return PipelineResult.failure(PipelineFailure.warning(List.of(e.getClass().getName(), e.getMessage()))); } } - private PipelineResult> readEnvironment(URL url) throws IOException, URISyntaxException { - var submodelDescriptorsUrl = url.toURI().resolve(SUBMODEL_DESCRIPTORS_PATH).toURL(); - var shellDescriptorsUrl = url.toURI().resolve(SHELL_DESCRIPTORS_PATH).toURL(); - - Map environmentsByUrl = new HashMap<>(); - + private PipelineResult> readEnvironment(Registry registry) throws IOException { + Map environmentsByUrl = new HashMap<>(); Result> shellDescriptors; Result> submodelDescriptors; try { - shellDescriptors = readElements(shellDescriptorsUrl, AssetAdministrationShellDescriptor.class); - submodelDescriptors = readElements(submodelDescriptorsUrl, SubmodelDescriptor.class); + shellDescriptors = readElements(registry, registry.getShellDescriptorUrl(), AssetAdministrationShellDescriptor.class); + submodelDescriptors = readElements(registry, registry.getSubmodelDescriptorUrl(), SubmodelDescriptor.class); } catch (EdcException e) { // If an exception was raised, produce a fatal result - return PipelineResult.failure(PipelineFailure.fatal(List.of(e.getClass().getSimpleName()))); + return PipelineResult.failure(PipelineFailure.fatal(List.of(e.getClass().getSimpleName(), e.getMessage()))); } addShellDescriptors(environmentsByUrl, shellDescriptors.getContent()); addSubmodelDescriptors(environmentsByUrl, submodelDescriptors.getContent()); - Map environment = environmentsByUrl.entrySet().stream() + Map environment = environmentsByUrl.entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue() .build())); - if (shellDescriptors.failed() || submodelDescriptors.failed()) { return PipelineResult.recoverableFailure(environment, PipelineFailure.warning( @@ -122,7 +115,7 @@ private PipelineResult> readEnvironment(URL url) return PipelineResult.success(environment); } - private void addSubmodelDescriptors(Map environmentsByUrl, + private void addSubmodelDescriptors(Map environmentsByUrl, List submodelDescriptors) throws MalformedURLException { var submodelEndpointUrlsSorted = sortByHostAndPort(getEndpointUrls( submodelDescriptors.stream() @@ -136,18 +129,19 @@ private void addSubmodelDescriptors(Map environmentsByUrl, + private void addShellDescriptors(Map environmentsByUrl, List shellDescriptors) throws MalformedURLException { var shellEndpointUrlsSorted = sortByHostAndPort(getEndpointUrls( shellDescriptors.stream() @@ -161,15 +155,16 @@ private void addShellDescriptors(Map e aasServiceRegistry.register(shellUrl.toString()); for (AssetAdministrationShellDescriptor descriptor : shellDescriptors) { var baseUrl = getBaseUrl(shellUrl); + var service = new Service(baseUrl); var descriptorAsEnvironment = asEnvironment(descriptor); - var envBuilder = environmentsByUrl.getOrDefault(new AasAccessUrl(baseUrl), new DefaultEnvironment.Builder()); + var envBuilder = environmentsByUrl.getOrDefault(service, new DefaultEnvironment.Builder()); descriptorAsEnvironment.getAssetAdministrationShells().forEach(envBuilder::assetAdministrationShells); descriptorAsEnvironment.getSubmodels().forEach(envBuilder::submodels); - environmentsByUrl.put(new AasAccessUrl(baseUrl), envBuilder); + environmentsByUrl.put(service, envBuilder); } } diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/agent/impl/ServiceAgent.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/agent/impl/ServiceAgent.java index 2c31c61a..277b18c1 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/agent/impl/ServiceAgent.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/agent/impl/ServiceAgent.java @@ -17,6 +17,7 @@ import de.fraunhofer.iosb.aas.AasDataProcessorFactory; import de.fraunhofer.iosb.app.aas.agent.AasAgent; +import de.fraunhofer.iosb.app.model.aas.service.Service; import de.fraunhofer.iosb.app.pipeline.PipelineFailure; import de.fraunhofer.iosb.app.pipeline.PipelineResult; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; @@ -29,18 +30,12 @@ import org.eclipse.edc.spi.result.Result; import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; import java.util.List; /** * Communicating with AAS service */ -public class ServiceAgent extends AasAgent { - - public static final String SUBMODELS_PATH = "%s/submodels".formatted(AAS_V3_PREFIX); - public static final String SHELLS_PATH = "%s/shells".formatted(AAS_V3_PREFIX); - public static final String CONCEPT_DESCRIPTIONS_PATH = "%s/concept-descriptions".formatted(AAS_V3_PREFIX); +public class ServiceAgent extends AasAgent { public ServiceAgent(AasDataProcessorFactory aasDataProcessorFactory) { super(aasDataProcessorFactory); @@ -49,32 +44,28 @@ public ServiceAgent(AasDataProcessorFactory aasDataProcessorFactory) { /** * Returns the environment of an AAS service. * - * @param url The AAS service's access URL + * @param service AAS service provider details * @return A map with one entry. This entry is the access url and environment of the service */ @Override - public PipelineResult apply(URL url) { + public PipelineResult apply(Service service) { try { - return readEnvironment(url); + return readEnvironment(service); } catch (Exception e) { // uncaught exception! return PipelineResult.failure(PipelineFailure.warning(List.of(e.getClass().getSimpleName(), e.getMessage()))); } } - private PipelineResult readEnvironment(URL aasServiceUrl) throws IOException, URISyntaxException { - var submodelUrl = aasServiceUrl.toURI().resolve(SUBMODELS_PATH).toURL(); - var shellsUrl = aasServiceUrl.toURI().resolve(SHELLS_PATH).toURL(); - var conceptDescriptionsUrl = aasServiceUrl.toURI().resolve(CONCEPT_DESCRIPTIONS_PATH).toURL(); + private PipelineResult readEnvironment(Service service) throws IOException { Result> shellsResult; Result> submodelsResult; Result> conceptDescriptionsResult; try { - shellsResult = readElements(shellsUrl, AssetAdministrationShell.class); - submodelsResult = readElements(submodelUrl, Submodel.class); - conceptDescriptionsResult = readElements(conceptDescriptionsUrl, ConceptDescription.class); - + shellsResult = readElements(service, service.getShellsUrl(), AssetAdministrationShell.class); + submodelsResult = readElements(service, service.getSubmodelsUrl(), Submodel.class); + conceptDescriptionsResult = readElements(service, service.getConceptDescriptionsUrl(), ConceptDescription.class); } catch (EdcException e) { // If an exception was raised, produce a fatal result return PipelineResult.failure(PipelineFailure.fatal(List.of(e.getClass().getSimpleName()))); diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/controller/AasController.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/controller/AasController.java index f6cef9c2..a6568d57 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/controller/AasController.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/controller/AasController.java @@ -101,22 +101,22 @@ public void stopServices() { @Override public void created(Service service) { - serviceRegistry.register(service.accessUrl().toString()); + serviceRegistry.register(service.getAccessUrl().toString()); } @Override public void created(Registry registry) { - serviceRegistry.register(registry.accessUrl().toString()); + serviceRegistry.register(registry.getAccessUrl().toString()); } @Override public void removed(Service service) { - serviceRegistry.unregister(service.accessUrl().toString()); - stopService(service.accessUrl()); + serviceRegistry.unregister(service.getAccessUrl().toString()); + stopService(service.getAccessUrl()); } @Override public void removed(Registry registry) { - serviceRegistry.unregister(registry.accessUrl().toString()); + serviceRegistry.unregister(registry.getAccessUrl().toString()); } } diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/Registry.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/Registry.java index 908ded94..bdfd9c2a 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/Registry.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/Registry.java @@ -16,31 +16,92 @@ package de.fraunhofer.iosb.app.model.aas.registry; import de.fraunhofer.iosb.app.model.aas.service.Service; -import de.fraunhofer.iosb.model.aas.AasAccessUrl; +import de.fraunhofer.iosb.model.aas.AasProvider; +import de.fraunhofer.iosb.model.aas.auth.AuthenticationMethod; +import de.fraunhofer.iosb.model.aas.net.AasAccessUrl; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; import java.util.Collection; -import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** - * An AAS registry as seen in FA³ST Registry - * - * @param accessUrl URL for accessing the registry. - * @param services The AAS services offered by this registry. + * An AAS registry representation as seen in FA³ST Registry */ -public record Registry(@Nonnull AasAccessUrl accessUrl, @Nullable Collection services) { - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Registry registry = (Registry) o; +public final class Registry extends AasProvider { + + public static final String SUBMODEL_DESCRIPTORS_PATH = "%s/submodel-descriptors".formatted(AAS_V3_PREFIX); + public static final String SHELL_DESCRIPTORS_PATH = "%s/shell-descriptors".formatted(AAS_V3_PREFIX); + + @Nullable + private Collection services; + + /** + * Create a new AAS registry representation with given access url and environment and no required + * authentication method. + * + * @param accessUrl URL for accessing the registry. + * @param services The AAS services offered by this registry. + */ + public Registry(@Nonnull URL accessUrl, @Nullable Collection services) { + super(new AasAccessUrl(accessUrl)); + this.services = services; + } + + /** + * Create a new AAS registry representation with given access url and empty (nonnull) environment and no required + * authentication method. + * + * @param accessUrl URL for accessing the registry. + */ + public Registry(@Nonnull URL accessUrl) { + super(new AasAccessUrl(accessUrl)); + this.services = new ArrayList<>(); + } + + public Registry with(Collection services) { + this.services = services; + return this; + } + + /** + * Create a new AAS registry representation with given access url and environment and + * authentication method. + * + * @param accessUrl URL for accessing the registry. + * @param services The AAS services offered by this registry. + */ + public Registry(@Nonnull URL accessUrl, @Nullable Collection services, AuthenticationMethod authenticationMethod) { + super(new AasAccessUrl(accessUrl), authenticationMethod); + this.services = services; + } + + public URL getSubmodelDescriptorUrl() throws MalformedURLException { + return new URL(getAccessUrl(), SUBMODEL_DESCRIPTORS_PATH); + } - return Objects.equals(accessUrl, registry.accessUrl); + public URL getShellDescriptorUrl() throws MalformedURLException { + return new URL(getAccessUrl(), SHELL_DESCRIPTORS_PATH); + } + + + /** + * Returns services this registry holds. This can be null before synchronization happened + * + * @return The services this registry has registered. + */ + @Nullable + public Collection services() { + return services; } @Override - public int hashCode() { - return Objects.hashCode(accessUrl); + public String toString() { + return "Registry[" + + "accessUrl=" + super.getAccessUrl() + ", " + + "services=" + services + ']'; } + } diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/RegistryRepository.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/RegistryRepository.java index 668fc68b..f99d64eb 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/RegistryRepository.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/RegistryRepository.java @@ -17,11 +17,9 @@ import de.fraunhofer.iosb.app.model.aas.service.Service; import de.fraunhofer.iosb.app.model.ids.SelfDescriptionChangeListener; -import de.fraunhofer.iosb.model.aas.AasAccessUrl; import org.eclipse.edc.spi.observe.ObservableImpl; import java.net.URL; -import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Objects; @@ -43,7 +41,7 @@ public RegistryRepository() { * @return True if created, else false. */ public boolean create(URL accessUrl) { - var registry = new Registry(new AasAccessUrl(accessUrl), new ArrayList<>()); + var registry = new Registry(accessUrl); if (registries.add(registry)) { invokeForEach(listener -> listener.created(registry)); @@ -57,11 +55,8 @@ public boolean create(URL accessUrl) { * * @return URLs of all registries currently stored. */ - public Collection getAllUrls() { - return registries.stream() - .map(Registry::accessUrl) - .map(AasAccessUrl::url) - .toList(); + public Collection getAll() { + return registries; } /** @@ -81,7 +76,7 @@ public Collection getAllEnvironments() { */ public @Nullable Collection getEnvironments(URL registryUrl) { return getEnvironments(registry -> - registry.accessUrl().toString() + registry.getAccessUrl().toString() .equals(registryUrl.toString())); } @@ -102,8 +97,8 @@ public void update(Registry toUpdate) { */ public boolean delete(URL accessUrl) { // Before we remove the registry, notify listeners (remove assets/contracts from edc) - var registry = registries.stream() - .filter(r -> r.accessUrl().equals(new AasAccessUrl(accessUrl))) + Registry registry = registries.stream() + .filter(r -> r.getAccessUrl().toString().equals(accessUrl.toString())) .findFirst() .orElse(null); diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/RegistryRepositoryUpdater.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/RegistryRepositoryUpdater.java index 7a6f8e19..d57d1e07 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/RegistryRepositoryUpdater.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/RegistryRepositoryUpdater.java @@ -47,7 +47,7 @@ public RegistryRepositoryUpdater(RegistryRepository registryRepository) { public PipelineResult>> apply(Collection registries) { Collection> result = new ArrayList<>(); registries.forEach(registry -> { - var storedEnvironments = Optional.ofNullable(registryRepository.getEnvironments(registry.accessUrl().url())).orElse(List.of()); + var storedEnvironments = Optional.ofNullable(registryRepository.getEnvironments(registry.getAccessUrl())).orElse(List.of()); Optional.ofNullable(registry.services()) .orElse(List.of()) @@ -69,6 +69,6 @@ public PipelineResult>> apply(Collection return services.stream() .filter(toFind::equals) .findFirst() - .orElse(new Service(toFind.accessUrl(), null)); + .orElse(new Service(toFind.getAccessUrl())); } } diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/Service.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/Service.java index 888abc2c..1a86ac4d 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/Service.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/Service.java @@ -15,37 +15,74 @@ */ package de.fraunhofer.iosb.app.model.aas.service; +import de.fraunhofer.iosb.model.aas.AasProvider; +import de.fraunhofer.iosb.model.aas.auth.AuthenticationMethod; +import de.fraunhofer.iosb.model.aas.net.AasAccessUrl; import org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset; +import org.jetbrains.annotations.NotNull; -import java.net.URISyntaxException; +import java.net.MalformedURLException; import java.net.URL; -import java.util.Objects; +import javax.annotation.Nullable; /** - * An AAS service as seen in FA³ST Service - * - * @param accessUrl URL for accessing the service. - * @param environment The AAS environment in asset form. + * An AAS service representation as seen in FA³ST Service */ -public record Service(URL accessUrl, Asset environment) { +public final class Service extends AasProvider { + + public static final String SHELLS_PATH = "%s/shells".formatted(AAS_V3_PREFIX); + public static final String SUBMODELS_PATH = "%s/submodels".formatted(AAS_V3_PREFIX); + public static final String CONCEPT_DESCRIPTIONS_PATH = "%s/concept-descriptions".formatted(AAS_V3_PREFIX); + + @Nullable + private Asset environment; /** - * Only checks for accessUrl! + * Create a new service with given access url and no environment and no required authentication. + * + * @param accessUrl URL for accessing the service. */ - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Service service = (Service) o; - try { - return Objects.equals(accessUrl.toURI(), service.accessUrl.toURI()); - } catch (URISyntaxException e) { - return false; - } + public Service(URL accessUrl) { + super(new AasAccessUrl(accessUrl)); + this.environment = null; + } + + /** + * Create a new service representation with given access url and empty environment and given authentication method. + * + * @param accessUrl URL for accessing the service. + * @param authenticationMethod The authentication method required to access this AAS service + */ + public Service(URL accessUrl, AuthenticationMethod authenticationMethod) { + super(new AasAccessUrl(accessUrl), authenticationMethod); + this.environment = null; + } + + public @NotNull Service with(Asset environment) { + this.environment = environment; + return this; + } + + public Asset environment() { + return environment; } @Override - public int hashCode() { - return Objects.hashCode(accessUrl); + public String toString() { + return "Service[" + + "accessUrl=" + super.getAccessUrl() + ", " + + "environment=" + environment + ']'; + } + + public URL getShellsUrl() throws MalformedURLException { + return new URL(getAccessUrl(), SHELLS_PATH); + } + + public URL getSubmodelsUrl() throws MalformedURLException { + return new URL(getAccessUrl(), SUBMODELS_PATH); + } + + public URL getConceptDescriptionsUrl() throws MalformedURLException { + return new URL(getAccessUrl(), CONCEPT_DESCRIPTIONS_PATH); } } diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/ServiceRepository.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/ServiceRepository.java index 87a6b3c3..5262b3b5 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/ServiceRepository.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/ServiceRepository.java @@ -38,11 +38,10 @@ public ServiceRepository() { services = new HashSet<>(); } - public Collection getAllServiceAccessUrls() { - return services.stream().map(Service::accessUrl).toList(); + public Collection getAll() { + return services; } - /** * Returns the environments offered by all stored AAS services. * If a stored AAS service has no environment, the element is filtered out. @@ -67,7 +66,7 @@ public Collection getAllEnvironments() { public @Nullable Asset getEnvironment(URL serviceUrl) { return services.stream() .filter(service -> - service.accessUrl().toString() + service.getAccessUrl().toString() .equals(serviceUrl.toString())) .findAny() .orElseThrow(() -> @@ -82,7 +81,7 @@ public Collection getAllEnvironments() { * @return True if created, else false. */ public boolean create(URL accessUrl) { - var service = new Service(accessUrl, null); + var service = new Service(accessUrl); if (services.add(service)) { invokeForEach(listener -> listener.created(service)); @@ -108,8 +107,8 @@ public void updateService(Service service) { */ public boolean delete(URL accessUrl) { // Before we remove the service, notify listeners (remove assets/contracts from edc) - var service = services.stream() - .filter(s -> s.accessUrl().toString().equals(accessUrl.toString())) + Service service = services.stream() + .filter(s -> s.getAccessUrl().toString().equals(accessUrl.toString())) .findFirst() .orElse(null); diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/ServiceRepositoryUpdater.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/ServiceRepositoryUpdater.java index ac980130..0c93107d 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/ServiceRepositoryUpdater.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/ServiceRepositoryUpdater.java @@ -41,7 +41,7 @@ public ServiceRepositoryUpdater(ServiceRepository selfDescriptionRepository) { */ @Override public PipelineResult> apply(Service service) { - var old = selfDescriptionRepository.getEnvironment(service.accessUrl()); + var old = selfDescriptionRepository.getEnvironment(service.getAccessUrl()); selfDescriptionRepository.updateService(service); return PipelineResult.success(new Pair<>(old, service.environment())); diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/pipeline/helper/Filter.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/pipeline/helper/Filter.java new file mode 100644 index 00000000..fbe52d82 --- /dev/null +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/pipeline/helper/Filter.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige + * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten + * Forschung e.V. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.fraunhofer.iosb.app.pipeline.helper; + +import de.fraunhofer.iosb.app.pipeline.PipelineResult; +import de.fraunhofer.iosb.app.pipeline.PipelineStep; + +import java.util.Collection; +import java.util.function.Predicate; + +/** + * Filter an input collection. + * + * @param Type of input. + */ +public class Filter extends PipelineStep, Collection> { + + private final Predicate filterFunction; + + public Filter(Predicate filterFunction) { + this.filterFunction = filterFunction; + } + + @Override + public PipelineResult> apply(Collection ts) { + return PipelineResult.success(ts.stream().filter(filterFunction).toList()); + } +} diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/util/InetTools.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/util/InetTools.java index c8b6f1f4..ab32d970 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/util/InetTools.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/util/InetTools.java @@ -15,6 +15,8 @@ */ package de.fraunhofer.iosb.app.util; +import de.fraunhofer.iosb.model.aas.AasProvider; + import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; @@ -41,4 +43,11 @@ public static boolean pingHost(String host, int port, int timeout) { return false; // Either timeout or unreachable or failed DNS lookup. } } + + public static boolean pingHost(AasProvider provider) { + var host = provider.getAccessUrl().getHost(); + var port = provider.getAccessUrl().getPort(); + + return pingHost(host, port, 10); + } } diff --git a/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapperTest.java b/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapperTest.java index 5132b452..882cb2b8 100644 --- a/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapperTest.java +++ b/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapperTest.java @@ -18,7 +18,6 @@ import de.fraunhofer.iosb.app.model.aas.service.Service; import de.fraunhofer.iosb.app.pipeline.PipelineFailure; import de.fraunhofer.iosb.dataplane.aas.spi.AasDataAddress; -import de.fraunhofer.iosb.model.aas.AasAccessUrl; import de.fraunhofer.iosb.util.Encoder; import org.eclipse.digitaltwin.aas4j.v3.model.Environment; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultEnvironment; @@ -69,9 +68,9 @@ void testApplyNullEnvironment() throws MalformedURLException { var environment = getEnvironment(); var realEnvironmentAccessUrl = new URL("https://example.com"); - var input = new HashMap(); - input.put(toAasAccessUrl(realEnvironmentAccessUrl), environment); - input.put(toAasAccessUrl(accessUrl), null); + var input = new HashMap(); + input.put(new Service(realEnvironmentAccessUrl), environment); + input.put(new Service(accessUrl), null); var result = testSubject.apply(input); @@ -79,12 +78,12 @@ void testApplyNullEnvironment() throws MalformedURLException { assertTrue(result.failed()); assertEquals(PipelineFailure.Type.WARNING, result.getFailure().getFailureType()); assertNotNull(result.getContent().stream() - .filter(service -> service.accessUrl().toString() + .filter(service -> service.getAccessUrl().toString() .equals(realEnvironmentAccessUrl.toString())).findFirst().orElseThrow().environment()); assertNull(result.getContent().stream() - .filter(service -> service.accessUrl().toString().equals(accessUrl.toString())) - .findFirst().orElse(new Service(null, null)) + .filter(service -> service.getAccessUrl().toString().equals(accessUrl.toString())) + .findFirst().orElse(new Service(null)) .environment()); } @@ -93,8 +92,9 @@ void testApplyFaultyInput() throws MalformedURLException { var environment = getEnvironment(); var emptyEnvironment = getEmptyEnvironment(); - var input = new HashMap<>(Map.of(toAasAccessUrl(accessUrl), environment, toAasAccessUrl(new URL("http://localhost:8080")), emptyEnvironment)); - input.put(toAasAccessUrl(accessUrl), null); + var input = new HashMap<>(Map.of(new Service(accessUrl), environment, + new Service(new URL("http://localhost:8080")), emptyEnvironment)); + input.put(new Service(accessUrl), null); input.put(null, environment); var result = testSubject.apply(input); @@ -104,19 +104,15 @@ void testApplyFaultyInput() throws MalformedURLException { assertEquals(PipelineFailure.Type.FATAL, result.getFailure().getFailureType()); } - private AasAccessUrl toAasAccessUrl(URL url) { - return new AasAccessUrl(url); - } - @Test void testApply() { var env = getEnvironment(); - var result = testSubject.apply(Map.of(new AasAccessUrl(accessUrl), env)); + var result = testSubject.apply(Map.of(new Service(accessUrl), env)); assertTrue(result.succeeded()); assertNotNull(result.getContent()); var envAsset = result.getContent().stream() .filter(service -> service - .accessUrl().toString() + .getAccessUrl().toString() .equals(accessUrl.toString())) .map(Service::environment) .findFirst() @@ -144,7 +140,7 @@ void testNullAccessUrl() { @Test void testNullEnvironment() { - var res = testSubject.executeSingle(accessUrl, null); + var res = testSubject.executeSingle(new Service(accessUrl), null); assertTrue(res.failed()); } @@ -156,7 +152,7 @@ void testNullArgs() { @Test void testEmptyEnvironment() { - var result = testSubject.executeSingle(accessUrl, new DefaultEnvironment()).getContent(); + var result = testSubject.executeSingle(new Service(accessUrl), new DefaultEnvironment()).getContent(); assertEquals(emptyList, result.environment().getProperty(SUBMODELS)); assertEquals(emptyList, result.environment().getProperty(SHELLS)); @@ -169,7 +165,7 @@ void testOnlySubmodels() { var env = getEnvironment(); - var result = testSubject.executeSingle(accessUrl, env).getContent(); + var result = testSubject.executeSingle(new Service(accessUrl), env).getContent(); assertEquals(env.getSubmodels().size(), getChildren(result.environment(), SUBMODELS).size()); @@ -181,7 +177,7 @@ void testOnlySubmodels() { void testWholeEnvironmentIdEquality() { var env = getEnvironment(); - var result = testSubject.executeSingle(accessUrl, env); + var result = testSubject.executeSingle(new Service(accessUrl), env); assertTrue(result.succeeded()); @@ -200,7 +196,7 @@ void testWholeEnvironmentIdEquality() { @Test void testCorrectAccessUrls() { var env = getEnvironment(); - var result = testSubject.executeSingle(accessUrl, env).getContent(); + var result = testSubject.executeSingle(new Service(accessUrl), env).getContent(); var shellDataAddress = (AasDataAddress) getChildren(result.environment(), SHELLS).stream().map(Asset::getDataAddress).toList().get(0); var submodelDataAddress = @@ -210,13 +206,13 @@ void testCorrectAccessUrls() { assertTrue(shellDataAddress.getBaseUrl().startsWith(accessUrl.toString())); assertEquals("%s/%s".formatted(SHELLS, Encoder.encodeBase64(env.getAssetAdministrationShells().get(0).getId())), - shellDataAddress.referenceChainAsPath()); + shellDataAddress.getPath()); assertTrue(submodelDataAddress.getBaseUrl().startsWith(accessUrl.toString())); assertEquals("%s/%s".formatted(SUBMODELS, Encoder.encodeBase64(env.getSubmodels().get(0).getId())), - submodelDataAddress.referenceChainAsPath()); + submodelDataAddress.getPath()); assertTrue(conceptDescriptionDataAddress.getBaseUrl().startsWith(accessUrl.toString())); assertEquals("concept-descriptions/%s".formatted(Encoder.encodeBase64(env.getConceptDescriptions().get(0).getId())), - conceptDescriptionDataAddress.referenceChainAsPath()); + conceptDescriptionDataAddress.getPath()); } @SuppressWarnings("unchecked") diff --git a/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/agent/AasAgentTest.java b/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/agent/AasAgentTest.java index 99576f36..5a68fe53 100644 --- a/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/agent/AasAgentTest.java +++ b/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/agent/AasAgentTest.java @@ -17,10 +17,9 @@ import de.fraunhofer.iosb.aas.AasDataProcessorFactory; import de.fraunhofer.iosb.app.pipeline.PipelineResult; +import de.fraunhofer.iosb.model.aas.AasProvider; import org.junit.jupiter.api.Test; -import java.net.URL; - import static org.mockito.Mockito.mock; class AasAgentTest { @@ -30,7 +29,7 @@ void initializeTest() { // Abstract class. Try to instantiate with empty override of method new AasAgent<>(mock(AasDataProcessorFactory.class)) { @Override - public PipelineResult apply(URL url) { + public PipelineResult apply(AasProvider provider) { return null; } }; diff --git a/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/agent/impl/RegistryAgentTest.java b/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/agent/impl/RegistryAgentTest.java index 9a89affc..9387d761 100644 --- a/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/agent/impl/RegistryAgentTest.java +++ b/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/agent/impl/RegistryAgentTest.java @@ -16,7 +16,8 @@ package de.fraunhofer.iosb.app.aas.agent.impl; import de.fraunhofer.iosb.aas.impl.AllAasDataProcessorFactory; -import de.fraunhofer.iosb.model.aas.AasAccessUrl; +import de.fraunhofer.iosb.app.model.aas.registry.Registry; +import de.fraunhofer.iosb.app.model.aas.service.Service; import de.fraunhofer.iosb.registry.AasServiceRegistry; import de.fraunhofer.iosb.ssl.impl.NoOpSelfSignedCertificateRetriever; import dev.failsafe.RetryPolicy; @@ -36,8 +37,8 @@ import java.util.Optional; import static de.fraunhofer.iosb.api.model.HttpMethod.GET; -import static de.fraunhofer.iosb.app.aas.agent.impl.RegistryAgent.SHELL_DESCRIPTORS_PATH; -import static de.fraunhofer.iosb.app.aas.agent.impl.RegistryAgent.SUBMODEL_DESCRIPTORS_PATH; +import static de.fraunhofer.iosb.app.model.aas.registry.Registry.SHELL_DESCRIPTORS_PATH; +import static de.fraunhofer.iosb.app.model.aas.registry.Registry.SUBMODEL_DESCRIPTORS_PATH; import static de.fraunhofer.iosb.app.pipeline.PipelineFailure.Type.WARNING; import static de.fraunhofer.iosb.app.testutils.RegistryElementCreator.getEmptyShellDescriptor; import static de.fraunhofer.iosb.app.testutils.RegistryElementCreator.getEmptySubmodelDescriptor; @@ -98,7 +99,7 @@ void testApplyEmptyShellDescriptor() throws SerializationException, MalformedURL mockEmptySubmodelRequest(); - var result = testSubject.apply(mockServerUrl); + var result = testSubject.apply(new Registry(mockServerUrl)); assertTrue(result.succeeded()); @@ -107,7 +108,7 @@ void testApplyEmptyShellDescriptor() throws SerializationException, MalformedURL assertEquals(1, bodyAsEnvironment.size()); // We know the endpoint url from getEmptyShellDescriptor()... - var env = bodyAsEnvironment.get(new AasAccessUrl(new URL("https://localhost:12345"))); + var env = bodyAsEnvironment.get(new Service(new URL("https://localhost:12345"))); var shell = Optional.ofNullable(env.getAssetAdministrationShells().get(0)).orElseThrow(); @@ -130,7 +131,7 @@ void testApplyShellDescriptor() throws SerializationException, MalformedURLExcep mockEmptySubmodelRequest(); - var result = testSubject.apply(mockServerUrl); + var result = testSubject.apply(new Registry(mockServerUrl)); assertTrue(result.succeeded()); @@ -138,7 +139,7 @@ void testApplyShellDescriptor() throws SerializationException, MalformedURLExcep assertEquals(1, bodyAsEnvironment.size()); - var env = bodyAsEnvironment.get(new AasAccessUrl(new URL("https://localhost:12345"))); + var env = bodyAsEnvironment.get(new Service(new URL("https://localhost:12345"))); var shell = Optional.ofNullable(env.getAssetAdministrationShells().get(0)).orElseThrow(); @@ -161,7 +162,7 @@ void testApplyEmptySubmodelDescriptor() throws SerializationException, Malformed .respond(HttpResponse.response() .withBody(resultOf(submodelDescriptor))); - var result = testSubject.apply(mockServerUrl); + var result = testSubject.apply(new Registry(mockServerUrl, null)); assertTrue(result.succeeded()); @@ -170,7 +171,7 @@ void testApplyEmptySubmodelDescriptor() throws SerializationException, Malformed assertEquals(1, bodyAsEnvironment.size()); var submodel = Optional.ofNullable(Optional - .ofNullable(bodyAsEnvironment.get(new AasAccessUrl(new URL("https://localhost:12345")))) + .ofNullable(bodyAsEnvironment.get(new Service(new URL("https://localhost:12345")))) .orElseThrow() .getSubmodels() .get(0)) @@ -195,7 +196,7 @@ void testApplySubmodelDescriptor() throws SerializationException, MalformedURLEx .respond(HttpResponse.response() .withBody(resultOf(submodelDescriptor))); - var result = testSubject.apply(mockServerUrl); + var result = testSubject.apply(new Registry(mockServerUrl)); assertTrue(result.succeeded()); @@ -204,7 +205,7 @@ void testApplySubmodelDescriptor() throws SerializationException, MalformedURLEx assertEquals(1, bodyAsEnvironment.size()); var submodel = Optional.ofNullable(Optional - .ofNullable(bodyAsEnvironment.get(new AasAccessUrl(new URL("https://localhost:12345")))) + .ofNullable(bodyAsEnvironment.get(new Service(new URL("https://localhost:12345")))) .orElseThrow() .getSubmodels() .get(0)) @@ -219,7 +220,7 @@ void testApplySubmodelDescriptor() throws SerializationException, MalformedURLEx @Test void testApplyNotActuallyRegistry() throws MalformedURLException { - var result = testSubject.apply(new URL("https://example.com")); + var result = testSubject.apply(new Registry(new URL("https://example.com"))); assertTrue(result.failed()); assertEquals(WARNING, result.getFailure().getFailureType()); @@ -227,7 +228,7 @@ void testApplyNotActuallyRegistry() throws MalformedURLException { @Test void testApplyUnreachableRegistry() throws MalformedURLException { - var result = testSubject.apply(new URL("http://anonymous.invalid")); + var result = testSubject.apply(new Registry(new URL("http://anonymous.invalid"))); assertTrue(result.failed()); assertEquals(WARNING, result.getFailure().getFailureType()); diff --git a/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/agent/impl/ServiceAgentTest.java b/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/agent/impl/ServiceAgentTest.java index 444226f7..df89bb94 100644 --- a/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/agent/impl/ServiceAgentTest.java +++ b/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/agent/impl/ServiceAgentTest.java @@ -16,6 +16,7 @@ package de.fraunhofer.iosb.app.aas.agent.impl; import de.fraunhofer.iosb.aas.impl.AllAasDataProcessorFactory; +import de.fraunhofer.iosb.app.model.aas.service.Service; import de.fraunhofer.iosb.app.pipeline.PipelineResult; import de.fraunhofer.iosb.ssl.impl.NoOpSelfSignedCertificateRetriever; import dev.failsafe.RetryPolicy; @@ -35,9 +36,9 @@ import java.net.UnknownHostException; import static de.fraunhofer.iosb.api.model.HttpMethod.GET; -import static de.fraunhofer.iosb.app.aas.agent.impl.ServiceAgent.CONCEPT_DESCRIPTIONS_PATH; -import static de.fraunhofer.iosb.app.aas.agent.impl.ServiceAgent.SHELLS_PATH; -import static de.fraunhofer.iosb.app.aas.agent.impl.ServiceAgent.SUBMODELS_PATH; +import static de.fraunhofer.iosb.app.model.aas.service.Service.CONCEPT_DESCRIPTIONS_PATH; +import static de.fraunhofer.iosb.app.model.aas.service.Service.SHELLS_PATH; +import static de.fraunhofer.iosb.app.model.aas.service.Service.SUBMODELS_PATH; import static de.fraunhofer.iosb.app.pipeline.PipelineFailure.Type.FATAL; import static de.fraunhofer.iosb.app.pipeline.PipelineFailure.Type.WARNING; import static de.fraunhofer.iosb.app.testutils.AasCreator.getEmptyEnvironment; @@ -90,7 +91,7 @@ void testApplyEmptyEnvironment() { var emptyEnvironment = getEmptyEnvironment(); answerWith(emptyEnvironment); - PipelineResult result = testSubject.apply(mockServerUrl); + PipelineResult result = testSubject.apply(new Service(mockServerUrl)); assertTrue(result.succeeded()); assertNotNull(result.getContent()); @@ -105,7 +106,7 @@ void testApplyEnvironment() { var environment = getEnvironment(); answerWith(environment); - PipelineResult result = testSubject.apply(mockServerUrl); + PipelineResult result = testSubject.apply(new Service(mockServerUrl)); assertTrue(result.succeeded()); assertNotNull(result.getContent()); @@ -115,7 +116,7 @@ void testApplyEnvironment() { @Test void testApplyUnknownHost() throws MalformedURLException { - var result = testSubject.apply(new URL("http://anonymous.invalid")); + var result = testSubject.apply(new Service(new URL("http://anonymous.invalid"))); assertTrue(result.failed()); assertEquals(WARNING, result.getFailure().getFailureType()); @@ -124,7 +125,7 @@ void testApplyUnknownHost() throws MalformedURLException { @Test void testApplyUnreachable() throws MalformedURLException { - var result = testSubject.apply(new URL("http://localhost:" + getFreePort())); + var result = testSubject.apply(new Service(new URL("http://localhost:" + getFreePort()))); assertTrue(result.failed()); assertEquals(WARNING, result.getFailure().getFailureType()); @@ -134,7 +135,7 @@ void testApplyUnreachable() throws MalformedURLException { @Test void testApplyNotActuallyService() { // Here, mock server returns no valid response (it is not an AAS service) - var result = testSubject.apply(mockServerUrl); + var result = testSubject.apply(new Service(mockServerUrl)); assertTrue(result.failed()); assertEquals(FATAL, result.getFailure().getFailureType()); diff --git a/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/sync/SynchronizerTest.java b/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/sync/SynchronizerTest.java index a963f944..90a71fd4 100644 --- a/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/sync/SynchronizerTest.java +++ b/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/sync/SynchronizerTest.java @@ -17,6 +17,7 @@ import de.fraunhofer.iosb.app.aas.EnvironmentToAssetMapper; import de.fraunhofer.iosb.app.model.ChangeSet; +import de.fraunhofer.iosb.app.model.aas.service.Service; import de.fraunhofer.iosb.app.util.Pair; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultEnvironment; import org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset; @@ -70,8 +71,8 @@ void testApplyNewService() { var oldEnvironment = getEmptyEnvironment(); var newEnvironment = getEnvironment(); - var oldEnvironmentAsset = new EnvironmentToAssetMapper(() -> false).executeSingle(accessUrl, oldEnvironment); - var newEnvironmentAsset = new EnvironmentToAssetMapper(() -> false).executeSingle(accessUrl, newEnvironment); + var oldEnvironmentAsset = new EnvironmentToAssetMapper(() -> false).executeSingle(new Service(accessUrl), oldEnvironment); + var newEnvironmentAsset = new EnvironmentToAssetMapper(() -> false).executeSingle(new Service(accessUrl), newEnvironment); var pair = new Pair<>(oldEnvironmentAsset.getContent().environment(), newEnvironmentAsset.getContent().environment()); @@ -103,8 +104,8 @@ void testApplyRemoveSubmodel() { .conceptDescriptions(oldEnvironment.getConceptDescriptions()) .build(); - var oldEnvironmentAsset = new EnvironmentToAssetMapper(() -> false).executeSingle(accessUrl, oldEnvironment); - var newEnvironmentAsset = new EnvironmentToAssetMapper(() -> false).executeSingle(accessUrl, newEnvironment); + var oldEnvironmentAsset = new EnvironmentToAssetMapper(() -> false).executeSingle(new Service(accessUrl), oldEnvironment); + var newEnvironmentAsset = new EnvironmentToAssetMapper(() -> false).executeSingle(new Service(accessUrl), newEnvironment); var pair = new Pair<>(oldEnvironmentAsset.getContent().environment(), newEnvironmentAsset.getContent().environment()); From 8736342599de59e92ccd84ccada60978e880fe75 Mon Sep 17 00:00:00 2001 From: Carlos Schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Mon, 26 Aug 2024 16:13:23 +0200 Subject: [PATCH 07/17] Unify aspects of repositories --- .../de/fraunhofer/iosb/app/AasExtension.java | 5 +- .../java/de/fraunhofer/iosb/app/Endpoint.java | 50 ++++++---- .../app/model/aas/AasProviderRepository.java | 92 +++++++++++++++++++ .../iosb/app/model/aas/registry/Registry.java | 33 +++---- .../aas/registry/RegistryRepository.java | 78 ++++------------ .../iosb/app/model/aas/service/Service.java | 23 +++-- .../model/aas/service/ServiceRepository.java | 87 +++--------------- .../app/aas/agent/impl/RegistryAgentTest.java | 2 +- 8 files changed, 185 insertions(+), 185 deletions(-) create mode 100644 edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/AasProviderRepository.java diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/AasExtension.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/AasExtension.java index b247dc24..91555a93 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/AasExtension.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/AasExtension.java @@ -30,6 +30,7 @@ import de.fraunhofer.iosb.app.model.aas.registry.Registry; import de.fraunhofer.iosb.app.model.aas.registry.RegistryRepository; import de.fraunhofer.iosb.app.model.aas.registry.RegistryRepositoryUpdater; +import de.fraunhofer.iosb.app.model.aas.service.Service; import de.fraunhofer.iosb.app.model.aas.service.ServiceRepository; import de.fraunhofer.iosb.app.model.aas.service.ServiceRepositoryUpdater; import de.fraunhofer.iosb.app.model.configuration.Configuration; @@ -170,7 +171,7 @@ private void registerAasServicesByConfig(ServiceRepository selfDescriptionReposi var configInstance = Configuration.getInstance(); if (Objects.nonNull(configInstance.getRemoteAasLocation())) { - selfDescriptionRepository.create(configInstance.getRemoteAasLocation()); + selfDescriptionRepository.create(new Service(configInstance.getRemoteAasLocation())); } if (Objects.isNull(configInstance.getLocalAasModelPath())) { @@ -195,7 +196,7 @@ private void registerAasServicesByConfig(ServiceRepository selfDescriptionReposi return; } - selfDescriptionRepository.create(serviceUrl); + selfDescriptionRepository.create(new Service(serviceUrl)); } @Override diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/Endpoint.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/Endpoint.java index 2b6fb1ed..4f628ba6 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/Endpoint.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/Endpoint.java @@ -16,8 +16,12 @@ package de.fraunhofer.iosb.app; import de.fraunhofer.iosb.app.controller.AasController; +import de.fraunhofer.iosb.app.model.aas.AasProviderRepository; +import de.fraunhofer.iosb.app.model.aas.registry.Registry; import de.fraunhofer.iosb.app.model.aas.registry.RegistryRepository; +import de.fraunhofer.iosb.app.model.aas.service.Service; import de.fraunhofer.iosb.app.model.aas.service.ServiceRepository; +import de.fraunhofer.iosb.model.aas.AasProvider; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.DELETE; import jakarta.ws.rs.POST; @@ -36,10 +40,6 @@ import java.util.Objects; import javax.annotation.Nullable; -import static de.fraunhofer.iosb.app.model.aas.service.ServiceRepository.SelfDescriptionSourceType; -import static de.fraunhofer.iosb.app.model.aas.service.ServiceRepository.SelfDescriptionSourceType.REGISTRY; -import static de.fraunhofer.iosb.app.model.aas.service.ServiceRepository.SelfDescriptionSourceType.SERVICE; - /** * Delegates requests to controllers. */ @@ -83,7 +83,7 @@ public Endpoint(ServiceRepository serviceRepository, RegistryRepository registry @Path(REGISTRY_PATH) public Response createRegistry(@QueryParam("url") URL registryUrl) { monitor.info("POST /%s".formatted(REGISTRY_PATH)); - return createEntity(registryUrl, REGISTRY); + return createEntity(registryRepository, new Registry(registryUrl)); } /** @@ -96,7 +96,21 @@ public Response createRegistry(@QueryParam("url") URL registryUrl) { @Path(SERVICE_PATH) public Response createService(@QueryParam("url") URL serviceUrl) { monitor.info("POST /%s".formatted(SERVICE_PATH)); - return createEntity(serviceUrl, SERVICE); + return createEntity(serviceRepository, new Service(serviceUrl)); + } + + /** + * Register an AAS service to this extension + * + * @param provider The AAS provider + * @return Status message about the success of this operation. + */ + @POST + @Path(SERVICE_PATH) + public Response createProvider(AasProvider provider) { + monitor.info("POST /%s".formatted(SERVICE_PATH)); + var service = new Service(provider); + return createEntity(serviceRepository, service); } /** @@ -109,7 +123,7 @@ public Response createService(@QueryParam("url") URL serviceUrl) { @Path(REGISTRY_PATH) public Response removeRegistry(@QueryParam("url") URL registryUrl) { monitor.info("DELETE /%s".formatted(REGISTRY_PATH)); - return removeEntity(registryUrl, REGISTRY); + return removeEntity(registryRepository, registryUrl); } /** @@ -122,7 +136,7 @@ public Response removeRegistry(@QueryParam("url") URL registryUrl) { @Path(SERVICE_PATH) public Response removeService(@QueryParam("url") URL serviceUrl) { monitor.info("DELETE /%s".formatted(SERVICE_PATH)); - return removeEntity(serviceUrl, SERVICE); + return removeEntity(serviceRepository, serviceUrl); } /** @@ -158,7 +172,7 @@ public Response postAasEnvironment(@QueryParam("environment") String pathToEnvir } // From here, do the same as if it were a remote service. - try (var creationResponse = createEntity(serviceAccessUrl, SERVICE)) { + try (var creationResponse = createEntity(serviceRepository, new Service(serviceAccessUrl))) { if (creationResponse.getStatusInfo().getFamily().equals(Status.Family.SUCCESSFUL)) { return Response.status(Status.CREATED).entity(serviceAccessUrl).build(); } else { @@ -175,29 +189,27 @@ public Response postAasEnvironment(@QueryParam("environment") String pathToEnvir return java.nio.file.Path.of(pathAsString); } - private Response createEntity(URL accessUrl, SelfDescriptionSourceType type) { - if (Objects.isNull(accessUrl)) { + private Response createEntity(AasProviderRepository repository, T entity) { + if (entity == null) { return Response.status(Response.Status.BAD_REQUEST).entity("Missing query parameter 'url'").build(); } - if (SERVICE.equals(type) && serviceRepository.create(accessUrl) || - REGISTRY.equals(type) && registryRepository.create(accessUrl)) { - return Response.status(Status.CREATED).entity("Registered new AAS %s at EDC".formatted(type)).build(); + if (repository.create(entity)) { + return Response.status(Status.CREATED).entity("Registered new AAS %s at EDC".formatted(repository.contentType())).build(); } return Response.status(Status.CONFLICT) .entity("AAS %s with this URL is already registered." - .formatted(type.toString().toLowerCase())) + .formatted(repository.contentType().toLowerCase())) .build(); } - private Response removeEntity(URL accessUrl, SelfDescriptionSourceType type) { - if (Objects.isNull(accessUrl)) { + private Response removeEntity(AasProviderRepository repository, URL accessUrl) { + if (accessUrl == null) { return Response.status(Status.BAD_REQUEST).entity("Missing query parameter 'url'").build(); } - if (SERVICE.equals(type) && serviceRepository.delete(accessUrl) || - REGISTRY.equals(type) && registryRepository.delete(accessUrl)) { + if (repository.delete(accessUrl)) { // Return 204 (https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.5) return Response.noContent().build(); } else { diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/AasProviderRepository.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/AasProviderRepository.java new file mode 100644 index 00000000..e8e0f9a9 --- /dev/null +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/AasProviderRepository.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige + * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten + * Forschung e.V. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.fraunhofer.iosb.app.model.aas; + +import de.fraunhofer.iosb.app.model.ids.SelfDescriptionChangeListener; +import de.fraunhofer.iosb.model.aas.AasProvider; +import de.fraunhofer.iosb.model.aas.auth.AuthenticationMethod; +import org.eclipse.edc.spi.observe.ObservableImpl; + +import java.net.URL; +import java.util.Collection; +import java.util.concurrent.ConcurrentLinkedQueue; + +public abstract class AasProviderRepository extends ObservableImpl { + private final Collection content = new ConcurrentLinkedQueue<>(); + + /** + * Returns the contents of this repository. + * + * @return All elements currently stored in this repository. + */ + public Collection getAll() { + return this.content; + } + + /** + * Adds a new entity to the repository and notifies listeners. + * + * @param entity The new entity. + * @return True if created, else false. + */ + public boolean create(T entity) { + if (content.add(entity)) { + created(entity); + return true; + } + return false; + } + + /** + * Removes entity and notifies listeners. + * + * @param accessUrl URL of entity to be removed + */ + public boolean delete(URL accessUrl) { + T entity = content.stream() + .filter(s -> s.getAccessUrl().toString().equals(accessUrl.toString())) + .findFirst() + .orElse(null); + + if (entity != null) { + removed(entity); + return content.remove(entity); + } + return false; + } + + + /** + * Update an entity. Entities are identified by their accessUrls. + * + * @param entity Entity to update. + */ + public void update(T entity) { + content.remove(entity); + content.add(entity); + } + + /** + * Returns the name of the stored content type + * + * @return Stored content type as string + */ + public abstract String contentType(); + + protected abstract void created(T created); + + protected abstract void removed(T removed); +} diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/Registry.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/Registry.java index bdfd9c2a..4455686e 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/Registry.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/Registry.java @@ -39,43 +39,36 @@ public final class Registry extends AasProvider { private Collection services; /** - * Create a new AAS registry representation with given access url and environment and no required + * Create a new AAS registry representation with given access url and empty (nonnull) environment and no required * authentication method. * * @param accessUrl URL for accessing the registry. - * @param services The AAS services offered by this registry. */ - public Registry(@Nonnull URL accessUrl, @Nullable Collection services) { + public Registry(@Nonnull URL accessUrl) { super(new AasAccessUrl(accessUrl)); - this.services = services; + this.services = new ArrayList<>(); } /** - * Create a new AAS registry representation with given access url and empty (nonnull) environment and no required + * Create a new AAS registry representation with given access url and environment and no required * authentication method. * - * @param accessUrl URL for accessing the registry. + * @param accessUrl URL for accessing the registry. + * @param authenticationMethod The authentication needed by this registry. */ - public Registry(@Nonnull URL accessUrl) { - super(new AasAccessUrl(accessUrl)); - this.services = new ArrayList<>(); - } - - public Registry with(Collection services) { - this.services = services; - return this; + public Registry(@Nonnull URL accessUrl, @Nonnull AuthenticationMethod authenticationMethod) { + super(new AasAccessUrl(accessUrl), authenticationMethod); } /** - * Create a new AAS registry representation with given access url and environment and - * authentication method. + * Get this registry with the given services * - * @param accessUrl URL for accessing the registry. - * @param services The AAS services offered by this registry. + * @param services Services to be associated with the registry + * @return The updated registry. */ - public Registry(@Nonnull URL accessUrl, @Nullable Collection services, AuthenticationMethod authenticationMethod) { - super(new AasAccessUrl(accessUrl), authenticationMethod); + public Registry with(Collection services) { this.services = services; + return this; } public URL getSubmodelDescriptorUrl() throws MalformedURLException { diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/RegistryRepository.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/RegistryRepository.java index f99d64eb..7f611c8e 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/RegistryRepository.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/RegistryRepository.java @@ -15,49 +15,18 @@ */ package de.fraunhofer.iosb.app.model.aas.registry; +import de.fraunhofer.iosb.app.model.aas.AasProviderRepository; import de.fraunhofer.iosb.app.model.aas.service.Service; -import de.fraunhofer.iosb.app.model.ids.SelfDescriptionChangeListener; -import org.eclipse.edc.spi.observe.ObservableImpl; +import de.fraunhofer.iosb.model.aas.auth.AuthenticationMethod; import java.net.URL; import java.util.Collection; -import java.util.HashSet; import java.util.Objects; import java.util.function.Predicate; +import javax.annotation.Nonnull; import javax.annotation.Nullable; -public class RegistryRepository extends ObservableImpl { - private final Collection registries; - - public RegistryRepository() { - super(); - this.registries = new HashSet<>(); - } - - /** - * Adds a new registry to the repository with the given url. - * - * @param accessUrl Access URL of the new registry. - * @return True if created, else false. - */ - public boolean create(URL accessUrl) { - var registry = new Registry(accessUrl); - - if (registries.add(registry)) { - invokeForEach(listener -> listener.created(registry)); - return true; - } - return false; - } - - /** - * Returns all URLs of the stored registries - * - * @return URLs of all registries currently stored. - */ - public Collection getAll() { - return registries; - } +public class RegistryRepository extends AasProviderRepository { /** * Returns all stored environments of all registries @@ -74,44 +43,29 @@ public Collection getAllEnvironments() { * @param registryUrl The URL of the registry * @return The environments of this registry or null */ - public @Nullable Collection getEnvironments(URL registryUrl) { + public @Nullable Collection getEnvironments(@Nonnull URL registryUrl) { return getEnvironments(registry -> registry.getAccessUrl().toString() .equals(registryUrl.toString())); } - /** - * Update a registry. Registries are identified by their accessUrls - * - * @param toUpdate Registry with new content - */ - public void update(Registry toUpdate) { - registries.remove(toUpdate); - registries.add(toUpdate); + @Override + public String contentType() { + return Registry.class.getSimpleName(); } - /** - * Remove registry and notify listeners. - * - * @param accessUrl URL of registry to be removed - */ - public boolean delete(URL accessUrl) { - // Before we remove the registry, notify listeners (remove assets/contracts from edc) - Registry registry = registries.stream() - .filter(r -> r.getAccessUrl().toString().equals(accessUrl.toString())) - .findFirst() - .orElse(null); - - if (registry != null) { - invokeForEach(listener -> listener.removed(registry)); - return registries.remove(registry); - } + @Override + protected void created(Registry created) { + invokeForEach(listener -> listener.created(created)); + } - return false; + @Override + protected void removed(Registry removed) { + invokeForEach(listener -> listener.removed(removed)); } private Collection getEnvironments(Predicate registryPredicate) { - return registries.stream() + return getAll().stream() .filter(registryPredicate) .map(Registry::services) .filter(Objects::nonNull) diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/Service.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/Service.java index 1a86ac4d..02c11a57 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/Service.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/Service.java @@ -44,7 +44,6 @@ public final class Service extends AasProvider { */ public Service(URL accessUrl) { super(new AasAccessUrl(accessUrl)); - this.environment = null; } /** @@ -58,6 +57,15 @@ public Service(URL accessUrl, AuthenticationMethod authenticationMethod) { this.environment = null; } + /** + * Create a new service from another object + * + * @param provider Provider to replicate. + */ + public Service(AasProvider provider) { + super(provider); + } + public @NotNull Service with(Asset environment) { this.environment = environment; return this; @@ -67,12 +75,6 @@ public Asset environment() { return environment; } - @Override - public String toString() { - return "Service[" + - "accessUrl=" + super.getAccessUrl() + ", " + - "environment=" + environment + ']'; - } public URL getShellsUrl() throws MalformedURLException { return new URL(getAccessUrl(), SHELLS_PATH); @@ -85,4 +87,11 @@ public URL getSubmodelsUrl() throws MalformedURLException { public URL getConceptDescriptionsUrl() throws MalformedURLException { return new URL(getAccessUrl(), CONCEPT_DESCRIPTIONS_PATH); } + + @Override + public String toString() { + return "Service[" + + "accessUrl=" + super.getAccessUrl() + ", " + + "environment=" + environment + ']'; + } } diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/ServiceRepository.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/ServiceRepository.java index 5262b3b5..fb9eaeac 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/ServiceRepository.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/ServiceRepository.java @@ -15,13 +15,11 @@ */ package de.fraunhofer.iosb.app.model.aas.service; -import de.fraunhofer.iosb.app.model.ids.SelfDescriptionChangeListener; +import de.fraunhofer.iosb.app.model.aas.AasProviderRepository; import org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset; -import org.eclipse.edc.spi.observe.ObservableImpl; import java.net.URL; import java.util.Collection; -import java.util.HashSet; import java.util.Objects; import javax.annotation.Nullable; @@ -29,27 +27,10 @@ * Self-description repository, also an observable so that on removal * of self-description, AssetIndex / ContractStore can be synchronized */ -public class ServiceRepository extends ObservableImpl { +public class ServiceRepository extends AasProviderRepository { - private final Collection services; - - public ServiceRepository() { - super(); - services = new HashSet<>(); - } - - public Collection getAll() { - return services; - } - - /** - * Returns the environments offered by all stored AAS services. - * If a stored AAS service has no environment, the element is filtered out. - * - * @return All environments currently stored for all registered AAS services. - */ public Collection getAllEnvironments() { - return services.stream() + return getAll().stream() .map(Service::environment) .filter(Objects::nonNull) .toList(); @@ -64,7 +45,7 @@ public Collection getAllEnvironments() { * @return The AAS environment in form of an EDC Asset. */ public @Nullable Asset getEnvironment(URL serviceUrl) { - return services.stream() + return getAll().stream() .filter(service -> service.getAccessUrl().toString() .equals(serviceUrl.toString())) @@ -74,60 +55,18 @@ public Collection getAllEnvironments() { .environment(); } - /** - * Adds a new service to the repository with the given url. - * - * @param accessUrl Access URL of the new service. - * @return True if created, else false. - */ - public boolean create(URL accessUrl) { - var service = new Service(accessUrl); - - if (services.add(service)) { - invokeForEach(listener -> listener.created(service)); - return true; - } - return false; - } - - /** - * Update a service. Services are identified by their accessUrls. - * - * @param service Service to update. - */ - public void updateService(Service service) { - services.remove(service); - services.add(service); + @Override + public String contentType() { + return Service.class.getSimpleName(); } - /** - * Remove service and notify listeners. - * - * @param accessUrl URL of service to be removed - */ - public boolean delete(URL accessUrl) { - // Before we remove the service, notify listeners (remove assets/contracts from edc) - Service service = services.stream() - .filter(s -> s.getAccessUrl().toString().equals(accessUrl.toString())) - .findFirst() - .orElse(null); - - if (service != null) { - invokeForEach(listener -> listener.removed(service)); - return services.remove(service); - } - return false; + @Override + protected void created(Service created) { + invokeForEach(listener -> listener.created(created)); } - public enum SelfDescriptionSourceType { - /** - * An AAS service such as FA³ST - */ - SERVICE, - /** - * An AAS registry as specified in AAS documents - */ - REGISTRY + @Override + protected void removed(Service removed) { + invokeForEach(listener -> listener.removed(removed)); } - } diff --git a/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/agent/impl/RegistryAgentTest.java b/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/agent/impl/RegistryAgentTest.java index 9387d761..41fd0a46 100644 --- a/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/agent/impl/RegistryAgentTest.java +++ b/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/agent/impl/RegistryAgentTest.java @@ -162,7 +162,7 @@ void testApplyEmptySubmodelDescriptor() throws SerializationException, Malformed .respond(HttpResponse.response() .withBody(resultOf(submodelDescriptor))); - var result = testSubject.apply(new Registry(mockServerUrl, null)); + var result = testSubject.apply(new Registry(mockServerUrl)); assertTrue(result.succeeded()); From 733be913d4870ae65aa5470f60e26c0e602facef Mon Sep 17 00:00:00 2001 From: Carlos Schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Mon, 26 Aug 2024 16:14:53 +0200 Subject: [PATCH 08/17] Code cleanup --- .../java/de/fraunhofer/iosb/model/aas/AasProvider.java | 8 ++++++++ .../iosb/model/aas/auth/AuthenticationMethod.java | 1 + .../fraunhofer/iosb/app/aas/EnvironmentToAssetMapper.java | 3 ++- .../iosb/app/model/aas/AasProviderRepository.java | 1 - .../iosb/app/model/aas/registry/RegistryRepository.java | 1 - .../app/model/aas/service/ServiceRepositoryUpdater.java | 2 +- .../iosb/app/aas/EnvironmentToAssetMapperTest.java | 2 +- 7 files changed, 13 insertions(+), 5 deletions(-) diff --git a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/AasProvider.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/AasProvider.java index 76f7e2e3..3fec445e 100644 --- a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/AasProvider.java +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/AasProvider.java @@ -15,6 +15,7 @@ */ package de.fraunhofer.iosb.model.aas; +import com.fasterxml.jackson.annotation.JsonAlias; import de.fraunhofer.iosb.model.aas.auth.AuthenticationMethod; import de.fraunhofer.iosb.model.aas.auth.impl.NoAuth; import de.fraunhofer.iosb.model.aas.net.AasAccessUrl; @@ -28,7 +29,9 @@ public abstract class AasProvider { public static final String AAS_V3_PREFIX = "/api/v3.0"; + @JsonAlias("url") private final AasAccessUrl url; + @JsonAlias("auth") private final AuthenticationMethod authentication; public AasProvider(AasAccessUrl url) { @@ -41,6 +44,11 @@ public AasProvider(AasAccessUrl url, AuthenticationMethod authentication) { this.authentication = authentication; } + protected AasProvider(AasProvider from) { + this.url = from.url; + this.authentication = from.authentication; + } + public Map getHeaders() { var header = authentication.getHeader(); var responseMap = new HashMap(); diff --git a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/AuthenticationMethod.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/AuthenticationMethod.java index ef7df223..a1aaea5a 100644 --- a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/AuthenticationMethod.java +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/AuthenticationMethod.java @@ -22,6 +22,7 @@ public abstract class AuthenticationMethod { + // TODO serialize this so it's easy to embed in http request? how to handle different types of auth? /** * Get the header value to add to the request headers to communicate with the service. * Headers: [... , (getHeader().key, getHeader().value), ...] diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapper.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapper.java index c3721e93..f2b83bb3 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapper.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapper.java @@ -39,6 +39,7 @@ import org.jetbrains.annotations.NotNull; import java.net.MalformedURLException; +import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -79,7 +80,7 @@ public PipelineResult> apply(Map envir var results = environments.entrySet().stream() .map(entry -> executeSingle( Optional.ofNullable(entry.getKey()) - .orElse(new Service(null)), + .orElse(new Service((URL) null)), entry.getValue())).toList(); var contents = extractContents(results); diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/AasProviderRepository.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/AasProviderRepository.java index e8e0f9a9..aa2c6f6a 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/AasProviderRepository.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/AasProviderRepository.java @@ -17,7 +17,6 @@ import de.fraunhofer.iosb.app.model.ids.SelfDescriptionChangeListener; import de.fraunhofer.iosb.model.aas.AasProvider; -import de.fraunhofer.iosb.model.aas.auth.AuthenticationMethod; import org.eclipse.edc.spi.observe.ObservableImpl; import java.net.URL; diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/RegistryRepository.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/RegistryRepository.java index 7f611c8e..7bb4fcc7 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/RegistryRepository.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/registry/RegistryRepository.java @@ -17,7 +17,6 @@ import de.fraunhofer.iosb.app.model.aas.AasProviderRepository; import de.fraunhofer.iosb.app.model.aas.service.Service; -import de.fraunhofer.iosb.model.aas.auth.AuthenticationMethod; import java.net.URL; import java.util.Collection; diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/ServiceRepositoryUpdater.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/ServiceRepositoryUpdater.java index 0c93107d..2098434c 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/ServiceRepositoryUpdater.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/service/ServiceRepositoryUpdater.java @@ -42,7 +42,7 @@ public ServiceRepositoryUpdater(ServiceRepository selfDescriptionRepository) { @Override public PipelineResult> apply(Service service) { var old = selfDescriptionRepository.getEnvironment(service.getAccessUrl()); - selfDescriptionRepository.updateService(service); + selfDescriptionRepository.update(service); return PipelineResult.success(new Pair<>(old, service.environment())); } diff --git a/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapperTest.java b/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapperTest.java index 882cb2b8..1cbc957d 100644 --- a/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapperTest.java +++ b/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapperTest.java @@ -83,7 +83,7 @@ void testApplyNullEnvironment() throws MalformedURLException { assertNull(result.getContent().stream() .filter(service -> service.getAccessUrl().toString().equals(accessUrl.toString())) - .findFirst().orElse(new Service(null)) + .findFirst().orElse(new Service((URL) null)) .environment()); } From e3d131bd1a7178bf6e9074801ca534d0c50d84b8 Mon Sep 17 00:00:00 2001 From: Carlos Schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Mon, 26 Aug 2024 16:19:55 +0200 Subject: [PATCH 09/17] Fix null check --- .../src/main/java/de/fraunhofer/iosb/app/Endpoint.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/Endpoint.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/Endpoint.java index 4f628ba6..646ecfb9 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/Endpoint.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/Endpoint.java @@ -190,7 +190,7 @@ public Response postAasEnvironment(@QueryParam("environment") String pathToEnvir } private Response createEntity(AasProviderRepository repository, T entity) { - if (entity == null) { + if (entity == null || entity.getAccessUrl() == null) { return Response.status(Response.Status.BAD_REQUEST).entity("Missing query parameter 'url'").build(); } From d993ce3ef4c60a07196183b890e6def9b30c8471 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Fri, 30 Aug 2024 11:20:57 +0200 Subject: [PATCH 10/17] Add dependency check for other extensions --- .github/dependabot.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d159835e..4235e9cd 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,6 +5,16 @@ version: 2 updates: + - package-ecosystem: "gradle" + directory: "/client" + schedule: + interval: "daily" + + - package-ecosystem: "gradle" + directory: "/data-plane-aas" + schedule: + interval: "daily" + - package-ecosystem: "gradle" directory: "/edc-extension4aas" schedule: @@ -15,6 +25,11 @@ updates: schedule: interval: "daily" + - package-ecosystem: "gradle" + directory: "/public-api-management" + schedule: + interval: "daily" + # Maintain dependencies for GitHub Actions - package-ecosystem: "github-actions" directory: "/" From 76084ba205c26c652c093d65f7d792506dd5e444 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Thu, 5 Sep 2024 15:53:55 +0200 Subject: [PATCH 11/17] Add path for authed registration of AAS service --- .../src/main/java/de/fraunhofer/iosb/app/Endpoint.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/Endpoint.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/Endpoint.java index 646ecfb9..1e324219 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/Endpoint.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/Endpoint.java @@ -49,6 +49,7 @@ public class Endpoint { private static final String SERVICE_PATH = "service"; + private static final String SERVICE_AUTH_PATH = "service-auth"; // s.t. change private static final String REGISTRY_PATH = "registry"; private static final String ENVIRONMENT_PATH = "environment"; @@ -106,9 +107,9 @@ public Response createService(@QueryParam("url") URL serviceUrl) { * @return Status message about the success of this operation. */ @POST - @Path(SERVICE_PATH) + @Path(SERVICE_AUTH_PATH) public Response createProvider(AasProvider provider) { - monitor.info("POST /%s".formatted(SERVICE_PATH)); + monitor.info("POST /%s".formatted(SERVICE_AUTH_PATH)); var service = new Service(provider); return createEntity(serviceRepository, service); } From 2aae6fad62cf125cce8c494c5946270e85905c36 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:22:58 +0200 Subject: [PATCH 12/17] Add url availability check for non-ip --- .../fraunhofer/iosb/app/util/InetTools.java | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/util/InetTools.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/util/InetTools.java index ab32d970..adbecd47 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/util/InetTools.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/util/InetTools.java @@ -18,8 +18,10 @@ import de.fraunhofer.iosb.model.aas.AasProvider; import java.io.IOException; +import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.Socket; +import java.net.URL; public class InetTools { @@ -44,10 +46,30 @@ public static boolean pingHost(String host, int port, int timeout) { } } + private static boolean checkUrlAvailability(URL toCheck) { + try { + // Open basic http connection with "GET" method and check if IOException occurs + HttpURLConnection connection = (HttpURLConnection) toCheck.openConnection(); + connection.setRequestMethod("GET"); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + connection.getResponseCode(); + return true; + } catch (IOException e) { + return false; + } + } + + public static boolean pingHost(AasProvider provider) { var host = provider.getAccessUrl().getHost(); var port = provider.getAccessUrl().getPort(); + // If no port available, port should be 443 or 80 + if (port == -1) { + // Iff http:// go with port 80 + port = host.startsWith("http:") ? 80 : 443; + } - return pingHost(host, port, 10); + return pingHost(host, port, 10) || checkUrlAvailability(provider.getAccessUrl()); } } From 07f3d76f551ff4dd8a0a6e693ed6ddac32d01ded Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:24:46 +0200 Subject: [PATCH 13/17] Fix deserialization of auth methods --- .../model/aas/auth/AuthenticationMethod.java | 13 +++++++++- .../iosb/model/aas/auth/impl/BasicAuth.java | 25 +++++++++++++++---- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/AuthenticationMethod.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/AuthenticationMethod.java index a1aaea5a..4c7506b0 100644 --- a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/AuthenticationMethod.java +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/AuthenticationMethod.java @@ -15,14 +15,25 @@ */ package de.fraunhofer.iosb.model.aas.auth; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import de.fraunhofer.iosb.model.aas.auth.impl.ApiKey; +import de.fraunhofer.iosb.model.aas.auth.impl.BasicAuth; +import de.fraunhofer.iosb.model.aas.auth.impl.NoAuth; import org.jetbrains.annotations.Nullable; import java.util.AbstractMap; import java.util.Map; + +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = BasicAuth.class, name = "basic"), + @JsonSubTypes.Type(value = ApiKey.class, name = "api-key"), + @JsonSubTypes.Type(value = NoAuth.class) +}) public abstract class AuthenticationMethod { - // TODO serialize this so it's easy to embed in http request? how to handle different types of auth? /** * Get the header value to add to the request headers to communicate with the service. * Headers: [... , (getHeader().key, getHeader().value), ...] diff --git a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/impl/BasicAuth.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/impl/BasicAuth.java index 773b706c..8ebf1f58 100644 --- a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/impl/BasicAuth.java +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/impl/BasicAuth.java @@ -26,15 +26,30 @@ public class BasicAuth extends AuthenticationMethod { private final Base64.Encoder encoder = Base64.getEncoder(); - private final String username; - private final String password; + private String username; + private String password; - public BasicAuth(String username, String password) { - this.username = username; - this.password = password; + public BasicAuth() { } protected String getValue() { return "Basic %s".formatted(encoder.encodeToString("%s:%s".formatted(username, password).getBytes())); } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + } From 00fbcf471e3aeaf37ad4adf7fa587a1d31cc5f4a Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:25:18 +0200 Subject: [PATCH 14/17] Null checks --- .../java/de/fraunhofer/iosb/app/Endpoint.java | 21 ++++--------------- .../iosb/app/edc/CleanUpService.java | 4 +++- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/Endpoint.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/Endpoint.java index 1e324219..91b223cf 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/Endpoint.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/Endpoint.java @@ -22,6 +22,7 @@ import de.fraunhofer.iosb.app.model.aas.service.Service; import de.fraunhofer.iosb.app.model.aas.service.ServiceRepository; import de.fraunhofer.iosb.model.aas.AasProvider; +import de.fraunhofer.iosb.model.aas.auth.AuthenticationMethod; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.DELETE; import jakarta.ws.rs.POST; @@ -35,10 +36,10 @@ import org.eclipse.edc.spi.monitor.ConsoleMonitor; import org.eclipse.edc.spi.monitor.Monitor; +import javax.annotation.Nullable; import java.io.IOException; import java.net.URL; import java.util.Objects; -import javax.annotation.Nullable; /** * Delegates requests to controllers. @@ -49,7 +50,6 @@ public class Endpoint { private static final String SERVICE_PATH = "service"; - private static final String SERVICE_AUTH_PATH = "service-auth"; // s.t. change private static final String REGISTRY_PATH = "registry"; private static final String ENVIRONMENT_PATH = "environment"; @@ -95,22 +95,9 @@ public Response createRegistry(@QueryParam("url") URL registryUrl) { */ @POST @Path(SERVICE_PATH) - public Response createService(@QueryParam("url") URL serviceUrl) { + public Response createService(@QueryParam("url") URL serviceUrl, AuthenticationMethod auth) { monitor.info("POST /%s".formatted(SERVICE_PATH)); - return createEntity(serviceRepository, new Service(serviceUrl)); - } - - /** - * Register an AAS service to this extension - * - * @param provider The AAS provider - * @return Status message about the success of this operation. - */ - @POST - @Path(SERVICE_AUTH_PATH) - public Response createProvider(AasProvider provider) { - monitor.info("POST /%s".formatted(SERVICE_AUTH_PATH)); - var service = new Service(provider); + var service = auth == null ? new Service(serviceUrl) : new Service(serviceUrl, auth); return createEntity(serviceRepository, service); } diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/edc/CleanUpService.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/edc/CleanUpService.java index bddafc1f..257ac66d 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/edc/CleanUpService.java +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/edc/CleanUpService.java @@ -50,7 +50,9 @@ private CleanUpService(Pipeline servicePipeline) { @Override public void removed(Service service) { - servicePipeline.execute(service.environment()); + if (service.environment() != null) { + servicePipeline.execute(service.environment()); + } } @Override From e01dd2206512bb56f98c71b493c951927ca9311d Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:25:30 +0200 Subject: [PATCH 15/17] Clean up code --- .../main/java/de/fraunhofer/iosb/aas/AasDataProcessor.java | 1 - .../java/de/fraunhofer/iosb/model/aas/auth/impl/NoAuth.java | 3 +++ .../src/test/java/de/fraunhofer/iosb/app/EndpointTest.java | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/data-plane-aas/src/main/java/de/fraunhofer/iosb/aas/AasDataProcessor.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/aas/AasDataProcessor.java index 1ff4243c..1195d5dd 100644 --- a/data-plane-aas/src/main/java/de/fraunhofer/iosb/aas/AasDataProcessor.java +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/aas/AasDataProcessor.java @@ -84,7 +84,6 @@ private Response send(AasDataAddress aasDataAddress, byte[] bytes, String mediaT var requestPath = aasDataAddress.getPath(); - if (!requestPath.isEmpty()) { // Remove leading forward slash requestPath = requestPath.startsWith("/") ? requestPath.substring(1) : requestPath; diff --git a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/impl/NoAuth.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/impl/NoAuth.java index 7b7ceb95..dec4d6d1 100644 --- a/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/impl/NoAuth.java +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/impl/NoAuth.java @@ -21,6 +21,9 @@ public class NoAuth extends AuthenticationMethod { + public NoAuth() { + } + @Override public Map.Entry getHeader() { return null; diff --git a/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/EndpointTest.java b/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/EndpointTest.java index e6182aa5..bcab9419 100644 --- a/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/EndpointTest.java +++ b/edc-extension4aas/src/test/java/de/fraunhofer/iosb/app/EndpointTest.java @@ -83,7 +83,7 @@ void testCreateRegistry() { void testCreateService() { when(serviceRepositoryMock.create(any())).thenReturn(true); - try (var response = testSubject.createService(url)) { + try (var response = testSubject.createService(url, null)) { assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatusInfo().getStatusCode()); } @@ -125,7 +125,7 @@ void testCreateRegistryNullValue() { @Test void testCreateServiceNullValue() { when(serviceRepositoryMock.create(any())).thenThrow(IllegalAccessError.class); - try (var response = testSubject.createService(null)) { + try (var response = testSubject.createService(null, null)) { assertEquals(BAD_REQUEST.getStatusCode(), response.getStatusInfo().getStatusCode()); } From 00ca5154dc60bf6cc50425f84dc957b5870ac69c Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:27:47 +0200 Subject: [PATCH 16/17] Add auth to changelog.md --- changelog.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index a53396eb..20bc2812 100644 --- a/changelog.md +++ b/changelog.md @@ -2,7 +2,7 @@ ## Current development version -Compatibility: **Eclipse Dataspace Connector v0.8.1** +Compatibility: **Eclipse Dataspace Connector v0.8.1, v0.9.0** **New Features** @@ -14,6 +14,10 @@ Compatibility: **Eclipse Dataspace Connector v0.8.1** * When a contract is negotiated for one of those elements, the endpoint provided by the shell-/submodel-descriptor is used as data source for the data transfer +* Add AAS Authentication schemes + * If an external AAS service/registry needs authentication, this can be configured when registering the + service/registry at the extension + * example: `{ "type":"basic", "username": "admin", "password": "administrator" }` **Bugfixes** From d1ce0fad1f75e4e3eb21e4b6fdc36e14ed7bb226 Mon Sep 17 00:00:00 2001 From: carlos-schmidt <18703981+carlos-schmidt@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:28:09 +0200 Subject: [PATCH 17/17] Update edc version to 0.9.0 This required no changes in code, extension was already compatible --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index b1fa289d..43ba1f20 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ javaVersion=17 group=org.eclipse.edc -edcVersion=0.8.1 +edcVersion=0.9.0 faaastVersion=1.1.0 aas4jVersion=1.0.2 mockitoVersion=5.2.0