diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/pom.xml b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/pom.xml new file mode 100644 index 000000000..4ac78ce87 --- /dev/null +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.aasdiscoveryservice + ${revision} + + + basyx.aasdiscoveryservice-client + AAS Discovery Service Client + + + + org.eclipse.digitaltwin.basyx + basyx.aasdiscoveryservice-core + + + org.eclipse.digitaltwin.basyx + basyx.aasdiscoveryservice-core + test + tests + + + org.eclipse.digitaltwin.basyx + basyx.client + + + org.springframework + spring-test + test + + + org.springframework.boot + spring-boot-test + test + + + org.eclipse.digitaltwin.basyx + basyx.aasdiscoveryservice-backend-h2 + test + + + org.eclipse.digitaltwin.basyx + basyx.aasdiscoveryservice-backend + test + + + org.eclipse.digitaltwin.basyx + basyx.aasdiscoveryservice-http + test + + + org.eclipse.digitaltwin.basyx + basyx.aasdiscoveryservice-feature-authorization + test + + + org.eclipse.digitaltwin.basyx + basyx.http + test + tests + + + org.eclipse.digitaltwin.basyx + basyx.authorization + test + tests + + + + diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/client/AuthorizedConnectedAasDiscoveryService.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/client/AuthorizedConnectedAasDiscoveryService.java new file mode 100644 index 000000000..a78c728da --- /dev/null +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/client/AuthorizedConnectedAasDiscoveryService.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasdiscoveryservice.client; + +import org.eclipse.digitaltwin.basyx.client.internal.authorization.TokenManager; + +/** + * Provides access to an Authorized Aas Repository on a remote server + * + * @author fried + */ +public class AuthorizedConnectedAasDiscoveryService extends ConnectedAasDiscoveryService { + + private TokenManager tokenManager; + + public AuthorizedConnectedAasDiscoveryService(String baseURL, TokenManager tokenManager) { + super(baseURL, tokenManager); + this.tokenManager = tokenManager; + } + + public TokenManager getTokenManager() { + return tokenManager; + } + +} diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/client/ConnectedAasDiscoveryService.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/client/ConnectedAasDiscoveryService.java new file mode 100644 index 000000000..0e61e629a --- /dev/null +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/client/ConnectedAasDiscoveryService.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasdiscoveryservice.client; + +import org.eclipse.digitaltwin.aas4j.v3.model.SpecificAssetId; +import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.client.internal.AssetAdministrationShellDiscoveryApi; +import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.AasDiscoveryService; +import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.model.AssetLink; +import org.eclipse.digitaltwin.basyx.client.internal.ApiException; +import org.eclipse.digitaltwin.basyx.client.internal.authorization.TokenManager; +import org.eclipse.digitaltwin.basyx.core.exceptions.*; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.springframework.http.HttpStatus; + +import java.util.List; + +/** + * Provides access to an AAS Discovery Service on a remote server + * + * @author fried + */ +public class ConnectedAasDiscoveryService implements AasDiscoveryService { + + private final AssetAdministrationShellDiscoveryApi discoveryApi; + + public ConnectedAasDiscoveryService(String baseURL){ + this.discoveryApi = new AssetAdministrationShellDiscoveryApi(baseURL); + } + + public ConnectedAasDiscoveryService(String baseURL, TokenManager tokenManager){ + this.discoveryApi = new AssetAdministrationShellDiscoveryApi(baseURL, tokenManager); + } + + @Override + public CursorResult> getAllAssetAdministrationShellIdsByAssetLink(PaginationInfo pInfo, List assetIds) { + try{ + return discoveryApi.getAllAssetAdministrationShellIdsByAssetLink(assetIds, pInfo.getLimit(), pInfo.getCursor()); + } catch (ApiException e) { + if(e.getCode() == HttpStatus.NOT_FOUND.value()){ + throw new AssetLinkDoesNotExistException("No matching element for given assetIds"); + } else { + throw new RuntimeException("Error while getting all Asset Administration Shell IDs by Asset Link", e); + } + } + } + + @Override + public List getAllAssetLinksById(String shellIdentifier) { + try{ + return discoveryApi.getAllAssetLinksById(shellIdentifier); + } catch (ApiException e) { + if(e.getCode() == HttpStatus.NOT_FOUND.value()){ + throw new AssetLinkDoesNotExistException("Element with id "+shellIdentifier+" does not exist"); + } else { + throw new RuntimeException("Error while getting all Asset Links by ID", e); + } + } + } + + @Override + public List createAllAssetLinksById(String shellIdentifier, List assetIds) { + try{ + return discoveryApi.postAllAssetLinksById(shellIdentifier, assetIds); + } catch (ApiException e) { + if(e.getCode() == HttpStatus.CONFLICT.value()){ + throw new CollidingAssetLinkException("Asset Links for shell "+shellIdentifier+" already exists"); + } else { + throw new RuntimeException("Error while creating all Asset Links by ID", e); + } + } + } + + @Override + public void deleteAllAssetLinksById(String shellIdentifier) { + try{ + discoveryApi.deleteAllAssetLinksById(shellIdentifier); + } catch (ApiException e) { + if(e.getCode() == HttpStatus.NOT_FOUND.value()){ + throw new AssetLinkDoesNotExistException("Element with id "+shellIdentifier+" does not exist"); + } else { + throw new RuntimeException("Error while deleting all Asset Links by ID", e); + } + } + } +} diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/client/internal/AssetAdministrationShellDiscoveryApi.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/client/internal/AssetAdministrationShellDiscoveryApi.java new file mode 100644 index 000000000..bed9b2e08 --- /dev/null +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/client/internal/AssetAdministrationShellDiscoveryApi.java @@ -0,0 +1,255 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.aasdiscoveryservice.client.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.StringJoiner; +import java.util.function.Consumer; + +import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.JsonMapperFactory; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.SimpleAbstractTypeResolverFactory; +import org.eclipse.digitaltwin.aas4j.v3.model.SpecificAssetId; +import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.model.AssetLink; +import org.eclipse.digitaltwin.basyx.client.internal.ApiClient; +import org.eclipse.digitaltwin.basyx.client.internal.ApiException; +import org.eclipse.digitaltwin.basyx.client.internal.ApiResponse; +import org.eclipse.digitaltwin.basyx.client.internal.Pair; +import org.eclipse.digitaltwin.basyx.client.internal.authorization.TokenManager; +import org.eclipse.digitaltwin.basyx.core.exceptions.AccessTokenRetrievalException; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; +import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursorResult; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.annotation.Generated; + +@Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen") +public class AssetAdministrationShellDiscoveryApi { + + private final HttpClient httpClient; + private final ObjectMapper objectMapper; + private final String baseUri; + private final Consumer requestInterceptor; + private final Duration readTimeout; + private final Consumer> responseInterceptor; + private final Consumer> asyncResponseInterceptor; + private TokenManager tokenManager; + + public AssetAdministrationShellDiscoveryApi() { + this(new ApiClient()); + } + + public AssetAdministrationShellDiscoveryApi(TokenManager tokenManager) { + this(new ApiClient()); + this.tokenManager = tokenManager; + } + + public AssetAdministrationShellDiscoveryApi(ApiClient apiClient) { + this.httpClient = apiClient.getHttpClient(); + this.objectMapper = apiClient.getObjectMapper(); + this.baseUri = apiClient.getBaseUri(); + this.requestInterceptor = apiClient.getRequestInterceptor(); + this.readTimeout = apiClient.getReadTimeout(); + this.responseInterceptor = apiClient.getResponseInterceptor(); + this.asyncResponseInterceptor = apiClient.getAsyncResponseInterceptor(); + } + + public AssetAdministrationShellDiscoveryApi(String baseUri) { + this(new ApiClient(HttpClient.newBuilder(), new JsonMapperFactory().create(new SimpleAbstractTypeResolverFactory().create()), baseUri)); + } + + public AssetAdministrationShellDiscoveryApi(String baseUri, TokenManager tokenManager) { + this(new ApiClient(HttpClient.newBuilder(), new JsonMapperFactory().create(new SimpleAbstractTypeResolverFactory().create()), baseUri)); + this.tokenManager = tokenManager; + } + + public CursorResult> getAllAssetAdministrationShellIdsByAssetLink(List assetIds, Integer limit, String cursor) throws ApiException { + ApiResponse>> response = getAllAssetAdministrationShellIdsByAssetLinkWithHttpInfo(assetIds, limit, cursor); + return response.getData(); + } + + public ApiResponse>> getAllAssetAdministrationShellIdsByAssetLinkWithHttpInfo(List assetIds, Integer limit, String cursor) throws ApiException { + HttpRequest.Builder requestBuilder = getAllShellIdsRequestBuilder(assetIds, limit, cursor); + try { + HttpResponse response = httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofInputStream()); + if (responseInterceptor != null) responseInterceptor.accept(response); + if (response.statusCode() / 100 != 2) throw getApiException("getAllAssetAdministrationShellIdsByAssetLink", response); + return new ApiResponse<>( + response.statusCode(), + response.headers().map(), + objectMapper.readValue(response.body(), new TypeReference>>() {}) + ); + } catch (IOException | InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + private HttpRequest.Builder getAllShellIdsRequestBuilder(List assetIds, Integer limit, String cursor) throws ApiException { + HttpRequest.Builder builder = HttpRequest.newBuilder(); + String path = "/lookup/shells"; + + List encodedAssetIds = new ArrayList<>(); + assetIds.forEach(assetLink -> { + try { + String assetLinkJson = objectMapper.writeValueAsString(assetLink); + encodedAssetIds.add(new Base64UrlEncodedIdentifier(assetLinkJson).getEncodedIdentifier()); + } catch (IOException e) { + throw new ApiException(e); + } + }); + + List queryParams = ApiClient.parameterToPairs("multi", "assetIds", encodedAssetIds); + queryParams.addAll(ApiClient.parameterToPairs("limit", limit)); + queryParams.addAll(ApiClient.parameterToPairs("cursor", cursor)); + + StringJoiner query = new StringJoiner("&"); + for (Pair p : queryParams) query.add(p.getName() + "=" + p.getValue()); + + builder.uri(URI.create(baseUri + path + (query.length() > 0 ? "?" + query : ""))); + builder.header("Accept", "application/json"); + addAuthorizationHeaderIfAuthIsEnabled(builder); + builder.method("GET", HttpRequest.BodyPublishers.noBody()); + if (readTimeout != null) builder.timeout(readTimeout); + if (requestInterceptor != null) requestInterceptor.accept(builder); + return builder; + } + + public List getAllAssetLinksById(String aasIdentifier) throws ApiException { + HttpRequest.Builder builder = HttpRequest.newBuilder(); + builder.uri(URI.create(baseUri + "/lookup/shells/" + new Base64UrlEncodedIdentifier(aasIdentifier).getEncodedIdentifier())); + builder.header("Accept", "application/json"); + addAuthorizationHeaderIfAuthIsEnabled(builder); + builder.method("GET", HttpRequest.BodyPublishers.noBody()); + if (readTimeout != null) builder.timeout(readTimeout); + if (requestInterceptor != null) requestInterceptor.accept(builder); + + try { + HttpResponse response = httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofInputStream()); + if (responseInterceptor != null) responseInterceptor.accept(response); + if (response.statusCode() / 100 != 2) + throw getApiException("getAllAssetLinksById", response); + return objectMapper.readValue(response.body(), new TypeReference>() {}); + } catch (IOException | InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + public List postAllAssetLinksById(String aasIdentifier, List assetIds) throws ApiException { + HttpRequest.Builder builder = HttpRequest.newBuilder(); + builder.uri(URI.create(baseUri + "/lookup/shells/" + new Base64UrlEncodedIdentifier(aasIdentifier).getEncodedIdentifier())); + builder.header("Content-Type", "application/json"); + builder.header("Accept", "application/json"); + addAuthorizationHeaderIfAuthIsEnabled(builder); + + try { + byte[] body = objectMapper.writeValueAsBytes(assetIds); + builder.method("POST", HttpRequest.BodyPublishers.ofByteArray(body)); + } catch (IOException e) { + throw new ApiException(e); + } + + if (readTimeout != null) builder.timeout(readTimeout); + if (requestInterceptor != null) requestInterceptor.accept(builder); + + try { + HttpResponse response = httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofInputStream()); + if (responseInterceptor != null) responseInterceptor.accept(response); + if (response.statusCode() / 100 != 2) + throw getApiException("postAllAssetLinksById", response); + return objectMapper.readValue(response.body(), new TypeReference>() {}); + } catch (IOException | InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + public void deleteAllAssetLinksById(String aasIdentifier) throws ApiException { + HttpRequest.Builder builder = HttpRequest.newBuilder(); + builder.uri(URI.create(baseUri + "/lookup/shells/" + new Base64UrlEncodedIdentifier(aasIdentifier).getEncodedIdentifier())); + builder.header("Accept", "application/json"); + addAuthorizationHeaderIfAuthIsEnabled(builder); + builder.method("DELETE", HttpRequest.BodyPublishers.noBody()); + if (readTimeout != null) builder.timeout(readTimeout); + if (requestInterceptor != null) requestInterceptor.accept(builder); + + try { + HttpResponse response = httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofInputStream()); + if (responseInterceptor != null) responseInterceptor.accept(response); + if (response.statusCode() / 100 != 2) + throw getApiException("deleteAllAssetLinksById", response); + } catch (IOException | InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + public Object getDescription() throws ApiException { + HttpRequest.Builder builder = HttpRequest.newBuilder(); + builder.uri(URI.create(baseUri + "/description")); + builder.header("Accept", "application/json"); + addAuthorizationHeaderIfAuthIsEnabled(builder); + builder.method("GET", HttpRequest.BodyPublishers.noBody()); + if (readTimeout != null) builder.timeout(readTimeout); + if (requestInterceptor != null) requestInterceptor.accept(builder); + + try { + HttpResponse response = httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofInputStream()); + if (responseInterceptor != null) responseInterceptor.accept(response); + if (response.statusCode() / 100 != 2) + throw getApiException("getDescription", response); + return objectMapper.readValue(response.body(), Object.class); + } catch (IOException | InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + private ApiException getApiException(String operationId, HttpResponse response) throws IOException { + String body = response.body() == null ? null : new String(response.body().readAllBytes()); + return new ApiException(response.statusCode(), operationId + " call failed with: " + response.statusCode() + " - " + (body != null ? body : "[no body]"), response.headers(), body); + } + + private void addAuthorizationHeaderIfAuthIsEnabled(HttpRequest.Builder builder) { + if (tokenManager != null) { + try { + builder.header("Authorization", "Bearer " + tokenManager.getAccessToken()); + } catch (IOException e) { + throw new AccessTokenRetrievalException("Unable to request access token"); + } + } + } +} diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/client/DummyAasDiscoveryServiceComponent.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/client/DummyAasDiscoveryServiceComponent.java new file mode 100644 index 000000000..679f38d32 --- /dev/null +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/client/DummyAasDiscoveryServiceComponent.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.aasdiscoveryservice.client; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Spring application configured for tests. + * + * @author fried + * + */ +@SpringBootApplication(scanBasePackages = "org.eclipse.digitaltwin.basyx") +public class DummyAasDiscoveryServiceComponent { + + public static void main(String[] args) { + SpringApplication.run(DummyAasDiscoveryServiceComponent.class, args); + } +} diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/client/DummyDiscoveryServiceConfig.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/client/DummyDiscoveryServiceConfig.java new file mode 100644 index 000000000..9d22361e2 --- /dev/null +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/client/DummyDiscoveryServiceConfig.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.aasdiscoveryservice.client; +import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.h2.backend.H2CrudAasDiscoveryFactory; +import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.AasDiscoveryService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Configuration for tests + * + * @author fried + * + */ +@Configuration +public class DummyDiscoveryServiceConfig { + + @Bean + AasDiscoveryService aasDiscoveryService(H2CrudAasDiscoveryFactory factory) { + return factory.create(); + } + +} diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/client/TestAuthorizedConnectedAasDiscovery.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/client/TestAuthorizedConnectedAasDiscovery.java new file mode 100644 index 000000000..e46157ad7 --- /dev/null +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/client/TestAuthorizedConnectedAasDiscovery.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasdiscoveryservice.client; + +import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.AasDiscoveryService; +import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.AasDiscoveryServiceSuite; +import org.eclipse.digitaltwin.basyx.client.internal.authorization.TokenManager; +import org.eclipse.digitaltwin.basyx.client.internal.authorization.credential.ClientCredential; +import org.eclipse.digitaltwin.basyx.client.internal.authorization.grant.ClientCredentialAccessTokenProvider; +import org.junit.After; +import org.junit.Before; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; + +public class TestAuthorizedConnectedAasDiscovery extends AasDiscoveryServiceSuite { + + private static ConfigurableApplicationContext appContext; + private static final String PROFILE = "authorization"; + + @Before + public void startAasDiscoveryService() { + SpringApplication application = new SpringApplication(DummyAasDiscoveryServiceComponent.class); + application.setAdditionalProfiles(PROFILE); + appContext = application.run(new String[] {}); + } + + @After + public void stopAasDiscoveryService() { + appContext.close(); + } + + @Override + protected AasDiscoveryService getAasDiscoveryService() { + return new AuthorizedConnectedAasDiscoveryService("http://localhost:7788", new TokenManager("http://localhost:9096/realms/BaSyx/protocol/openid-connect/token", new ClientCredentialAccessTokenProvider(new ClientCredential("workstation-1", "nY0mjyECF60DGzNmQUjL81XurSl8etom")))); + } + +} + diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/client/TestConnectedAasDiscovery.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/client/TestConnectedAasDiscovery.java new file mode 100644 index 000000000..7a3e5492a --- /dev/null +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/client/TestConnectedAasDiscovery.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasdiscoveryservice.client; + +import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.AasDiscoveryService; +import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.AasDiscoveryServiceSuite; +import org.junit.After; +import org.junit.Before; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * Tests the ConnectedAasDiscoveryService + * + * @author fried + */ +public class TestConnectedAasDiscovery extends AasDiscoveryServiceSuite { + + private static ConfigurableApplicationContext appContext; + + @Before + public void startAasDiscoveryService() { + appContext = new SpringApplicationBuilder(DummyAasDiscoveryServiceComponent.class).run(new String[] {}); + } + + @After + public void stopAasDiscoveryService() { + appContext.close(); + } + + @Override + protected AasDiscoveryService getAasDiscoveryService() { + return new ConnectedAasDiscoveryService("http://localhost:7788"); + } +} diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/test/resources/application-authorization.properties b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/test/resources/application-authorization.properties new file mode 100644 index 000000000..ba2278702 --- /dev/null +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/test/resources/application-authorization.properties @@ -0,0 +1,11 @@ +server.port=7788 +spring.application.name=AAS Discovery Service +basyx.aasdiscoveryservice.name=aas-discovery-service + +basyx.backend = InMemory + +basyx.feature.authorization.enabled = true +basyx.feature.authorization.type = rbac +basyx.feature.authorization.jwtBearerTokenProvider = keycloak +basyx.feature.authorization.rbac.file = classpath:rbac_rules.json +spring.security.oauth2.resourceserver.jwt.issuer-uri= http://localhost:9096/realms/BaSyx diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/test/resources/application.properties b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/test/resources/application.properties new file mode 100644 index 000000000..718625f1d --- /dev/null +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/test/resources/application.properties @@ -0,0 +1,5 @@ +server.port=7788 +spring.application.name=AAS Discovery Service +basyx.aasdiscoveryservice.name=aas-discovery-service + +basyx.backend=InMemory diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/test/resources/rbac_rules.json b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/test/resources/rbac_rules.json new file mode 100644 index 000000000..23eb2d992 --- /dev/null +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-client/src/test/resources/rbac_rules.json @@ -0,0 +1,43 @@ +[ + { + "role": "basyx-assetid-creator", + "action": "CREATE", + "targetInformation": { + "@type": "aas-discovery-service", + "aasIds": "*", + "assetIds": [] + } + }, + { + "role": "basyx-assetid-discoverer", + "action": "READ", + "targetInformation": { + "@type": "aas-discovery-service", + "aasIds": "*", + "assetIds": [] + } + }, + { + "role": "basyx-assetid-deleter", + "action": "DELETE", + "targetInformation": { + "@type": "aas-discovery-service", + "aasIds": "*", + "assetIds": [] + } + }, + { + "role": "basyx-aas-discoverer", + "action": "READ", + "targetInformation": { + "@type": "aas-discovery-service", + "aasIds": null, + "assetIds": [ + { + "name": "*", + "value": "*" + } + ] + } + } +] diff --git a/basyx.aasdiscoveryservice/pom.xml b/basyx.aasdiscoveryservice/pom.xml index 3b683c19f..92df94ddf 100644 --- a/basyx.aasdiscoveryservice/pom.xml +++ b/basyx.aasdiscoveryservice/pom.xml @@ -21,5 +21,6 @@ basyx.aasdiscoveryservice.component basyx.aasdiscoveryservice-feature-authorization basyx.aasdiscoveryservice-backend + basyx.aasdiscoveryservice-client diff --git a/pom.xml b/pom.xml index 290d0100d..07b15ef53 100644 --- a/pom.xml +++ b/pom.xml @@ -817,6 +817,11 @@ basyx.aasdiscoveryservice-feature-authorization ${revision} + + org.eclipse.digitaltwin.basyx + basyx.aasdiscoveryservice-client + ${revision} + org.eclipse.digitaltwin.basyx @@ -1292,6 +1297,12 @@ ${revision} tests + + org.eclipse.digitaltwin.basyx + basyx.aasdiscoveryservice-core + ${revision} + tests + org.eclipse.digitaltwin.basyx