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: "/" 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** 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..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 found 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()); } } 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..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 @@ -81,11 +81,16 @@ 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..3fec445e --- /dev/null +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/AasProvider.java @@ -0,0 +1,78 @@ +/* + * 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 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; + +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"; + + @JsonAlias("url") + private final AasAccessUrl url; + @JsonAlias("auth") + 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; + } + + protected AasProvider(AasProvider from) { + this.url = from.url; + this.authentication = from.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/aas/auth/AuthenticationMethod.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/AuthenticationMethod.java new file mode 100644 index 00000000..4c7506b0 --- /dev/null +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/AuthenticationMethod.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.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 { + + /** + * 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/aas/auth/impl/ApiKey.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/impl/ApiKey.java new file mode 100644 index 00000000..837997fd --- /dev/null +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/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.aas.auth.impl; + +import de.fraunhofer.iosb.model.aas.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/aas/auth/impl/BasicAuth.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/impl/BasicAuth.java new file mode 100644 index 00000000..8ebf1f58 --- /dev/null +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/impl/BasicAuth.java @@ -0,0 +1,55 @@ +/* + * 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.auth.impl; + +import de.fraunhofer.iosb.model.aas.auth.AuthenticationMethod; + +import java.util.Base64; + +/** + * rfc7617 + */ +public class BasicAuth extends AuthenticationMethod { + + private final Base64.Encoder encoder = Base64.getEncoder(); + + private String username; + private String 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; + } + +} 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 new file mode 100644 index 00000000..dec4d6d1 --- /dev/null +++ b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/auth/impl/NoAuth.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.aas.auth.impl; + +import de.fraunhofer.iosb.model.aas.auth.AuthenticationMethod; + +import java.util.Map; + +public class NoAuth extends AuthenticationMethod { + + public NoAuth() { + } + + @Override + public Map.Entry getHeader() { + return null; + } + + @Override + protected String getValue() { + return null; + } +} diff --git a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/AasAccessUrl.java b/data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/net/AasAccessUrl.java similarity index 97% rename from edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/AasAccessUrl.java rename to data-plane-aas/src/main/java/de/fraunhofer/iosb/model/aas/net/AasAccessUrl.java index e61f8613..38a132b7 100644 --- a/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/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.app.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/AasExtension.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/AasExtension.java index f1716301..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 @@ -27,20 +27,21 @@ 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; +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; 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.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.registry.AasServiceRegistry; import org.eclipse.edc.connector.controlplane.asset.spi.index.AssetIndex; @@ -59,9 +60,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.util.InetTools.pingHost; +import static de.fraunhofer.iosb.app.pipeline.PipelineFailure.Type.FATAL; /** * EDC Extension supporting usage of Asset Administration Shells. @@ -103,20 +105,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 +123,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() && 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())) + .map(registry -> registry.getKey().with(registry.getValue())) .toList())) .step(new RegistryRepositoryUpdater(registryRepository)) .step(new Synchronizer()) @@ -177,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())) { @@ -202,13 +196,12 @@ private void registerAasServicesByConfig(ServiceRepository selfDescriptionReposi return; } - selfDescriptionRepository.create(serviceUrl); + selfDescriptionRepository.create(new Service(serviceUrl)); } @Override public void shutdown() { // Gracefully stop AAS services - monitor.info("Stopping all internally started AAS services"); aasController.stopServices(); } } 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..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 @@ -16,8 +16,13 @@ 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 de.fraunhofer.iosb.model.aas.auth.AuthenticationMethod; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.DELETE; import jakarta.ws.rs.POST; @@ -31,14 +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; - -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 +84,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)); } /** @@ -94,9 +95,10 @@ 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(serviceUrl, SERVICE); + var service = auth == null ? new Service(serviceUrl) : new Service(serviceUrl, auth); + return createEntity(serviceRepository, service); } /** @@ -109,7 +111,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 +124,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 +160,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 +177,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 || entity.getAccessUrl() == 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/aas/EnvironmentToAssetMapper.java b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/aas/EnvironmentToAssetMapper.java index 2000a333..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 @@ -15,7 +15,6 @@ */ 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; @@ -39,6 +38,7 @@ import org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset; import org.jetbrains.annotations.NotNull; +import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; @@ -49,7 +49,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 +57,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 +76,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((URL) null)), entry.getValue())).toList(); var contents = extractContents(results); @@ -89,40 +88,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 +188,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 61247077..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 @@ -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.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 b996ed35..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,7 +17,8 @@ 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.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.registry.AasServiceRegistry; @@ -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/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 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..aa2c6f6a --- /dev/null +++ b/edc-extension4aas/src/main/java/de/fraunhofer/iosb/app/model/aas/AasProviderRepository.java @@ -0,0 +1,91 @@ +/* + * 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 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 33fc58fc..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 @@ -15,32 +15,86 @@ */ 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.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 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<>(); + } + + /** + * 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 authenticationMethod The authentication needed by this registry. + */ + public Registry(@Nonnull URL accessUrl, @Nonnull AuthenticationMethod authenticationMethod) { + super(new AasAccessUrl(accessUrl), authenticationMethod); + } - return Objects.equals(accessUrl, registry.accessUrl); + /** + * Get this registry with the given services + * + * @param services Services to be associated with the registry + * @return The updated registry. + */ + public Registry with(Collection services) { + this.services = services; + return this; + } + + public URL getSubmodelDescriptorUrl() throws MalformedURLException { + return new URL(getAccessUrl(), SUBMODEL_DESCRIPTORS_PATH); + } + + 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 e90c275b..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 @@ -15,54 +15,17 @@ */ package de.fraunhofer.iosb.app.model.aas.registry; -import de.fraunhofer.iosb.app.model.aas.AasAccessUrl; +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 java.net.URL; -import java.util.ArrayList; 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(new AasAccessUrl(accessUrl), new ArrayList<>()); - - 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 getAllUrls() { - return registries.stream() - .map(Registry::accessUrl) - .map(AasAccessUrl::url) - .toList(); - } +public class RegistryRepository extends AasProviderRepository { /** * Returns all stored environments of all registries @@ -79,44 +42,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.accessUrl().toString() + 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) - var registry = registries.stream() - .filter(r -> r.accessUrl().equals(new AasAccessUrl(accessUrl))) - .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/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..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 @@ -15,37 +15,83 @@ */ 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)); + } + + /** + * 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; + } + + /** + * 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; + } + + public Asset environment() { + return 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); } @Override - public int hashCode() { - return Objects.hashCode(accessUrl); + 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 87a6b3c3..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,28 +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 getAllServiceAccessUrls() { - return services.stream().map(Service::accessUrl).toList(); - } - - - /** - * 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(); @@ -65,9 +45,9 @@ 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.accessUrl().toString() + service.getAccessUrl().toString() .equals(serviceUrl.toString())) .findAny() .orElseThrow(() -> @@ -75,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, null); - - if (services.add(service)) { - invokeForEach(listener -> listener.created(service)); - return true; - } - return false; + @Override + public String contentType() { + return Service.class.getSimpleName(); } - /** - * 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 + protected void created(Service created) { + invokeForEach(listener -> listener.created(created)); } - /** - * 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) - var service = services.stream() - .filter(s -> s.accessUrl().toString().equals(accessUrl.toString())) - .findFirst() - .orElse(null); - - if (service != null) { - invokeForEach(listener -> listener.removed(service)); - return services.remove(service); - } - return false; + @Override + protected void removed(Service removed) { + invokeForEach(listener -> listener.removed(removed)); } - - public enum SelfDescriptionSourceType { - /** - * An AAS service such as FA³ST - */ - SERVICE, - /** - * An AAS registry as specified in AAS documents - */ - REGISTRY - } - } 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..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 @@ -41,8 +41,8 @@ public ServiceRepositoryUpdater(ServiceRepository selfDescriptionRepository) { */ @Override public PipelineResult> apply(Service service) { - var old = selfDescriptionRepository.getEnvironment(service.accessUrl()); - selfDescriptionRepository.updateService(service); + var old = selfDescriptionRepository.getEnvironment(service.getAccessUrl()); + selfDescriptionRepository.update(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..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 @@ -15,9 +15,13 @@ */ package de.fraunhofer.iosb.app.util; +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 { @@ -41,4 +45,31 @@ public static boolean pingHost(String host, int port, int timeout) { return false; // Either timeout or unreachable or failed DNS lookup. } } + + 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) || checkUrlAvailability(provider.getAccessUrl()); + } } 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()); } 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..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 @@ -15,7 +15,6 @@ */ 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; @@ -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((URL) 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 dbdbe089..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 @@ -16,7 +16,8 @@ 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.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)); 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()); diff --git a/example/resources/aas_edc_extension.postman_collection.json b/example/resources/aas_edc_extension.postman_collection.json index e94cccdd..bce979c0 100644 --- a/example/resources/aas_edc_extension.postman_collection.json +++ b/example/resources/aas_edc_extension.postman_collection.json @@ -338,6 +338,38 @@ }, "response": [] }, + { + "name": "Register authed AAS service", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"type\": \"basic\",\n \"username\": \"your-username\",\n \"password\": \"your-password\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{provider}}/service?url=https://authenticated-aas.com", + "host": [ + "{{provider}}" + ], + "path": [ + "service" + ], + "query": [ + { + "key": "url", + "value": "https://authenticated-aas.com" + } + ] + } + }, + "response": [] + }, { "name": "Register AAS registry by URL", "request": { @@ -369,7 +401,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"edc.aas.syncPeriod\": 10\n}", + "raw": "{\n \"edc.aas.syncPeriod\": 1\n}", "options": { "raw": { "language": "json" @@ -404,7 +436,7 @@ "query": [ { "key": "environment", - "value": "path/to/aas/model.aasx" + "value": "/path/to/aas/model.aasx" }, { "key": "port", 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